Initial commit of coda project.

Change-Id: I08fc75a7e7c22c5ce4126173df1576a7413bee19
This commit is contained in:
Nathaniel Kimball 2015-12-21 21:03:50 -07:00 committed by Nathaniel Kimball
parent 8f8e30a419
commit fede2601b8
66 changed files with 4028 additions and 0 deletions

33
README Normal file
View File

@ -0,0 +1,33 @@
Coda
======
Coda is a Horizon dashboard and panel (both share the name) that facilitates resource clean up of a project once that project is no longer needed http://openstack.org
Coda Dashboard
----------------
Coda Dashboard is an extension for OpenStack Dashboard that provides a UI for
Coda.
For developer purposes, please place OpenStack Dashboard extension file, located
at *local/_42_coda.py* under horizon/openstack_dashboard/local/enabled
directory and run horizon as usual.
You will need to add the following code to the end of your local_settings.py
def load_coda():
import imp
#Go up a level, coda should be installed there.
local_settings_dir = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]
local_settings_dir+="/dashboards/coda"
#print local_settings_dir
module = imp.load_source("coda", "%s/coda.py" % local_settings_dir)
for attr in dir(module):
if not attr.startswith('_'):
globals()[attr] = getattr(module, attr)
load_coda()
This makes sure that the coda.py file containing your settings are imported properly. This is probably a hack
but I haven't found a better way to do this yet.

6
__init__.py Normal file
View File

@ -0,0 +1,6 @@
"""The coda dashboard package.
Coda is a Horizon dashboard and panel (both share the name) that
facilitates resource clean up of a project once that project is no longer
needed http://openstack.org
"""

BIN
__init__.pyc Normal file

Binary file not shown.

51
coda.py Executable file
View File

@ -0,0 +1,51 @@
# Copyright [2015] Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# 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.
"""Settings for the coda panel.
There may be a better way or place to do this but for now this works
so I'm rolling with it.
"""
CODA_USERNAME = "coda_admin"
CODA_TENANT_NAME = "coda_admin"
CODA_TENANT_ID = "coda_project_id"
CODA_PASSWORD = "coda_pw"
CODA_AUTH_URL = "http://127.0.0.1:5000/v2.0/"
CODA_KEYSTONE_URL = "http://10.23.214.201:35357/v2.0/"
CODA_AUTH_URL_KEY = "CODA_AUTH_URL"
NOVA_ADMIN_URL_KEY = "NOVA_ADMIN_URL"
NEUTRON_ADMIN_URL_KEY = "NEUTRON_ADMIN_URL"
CINDER_ADMIN_URL_KEY = "CINDER_ADMIN_URL"
GLANCE_ADMIN_URL_KEY = "GLANCE_ADMIN_URL"
CODA_BLACK_LIST = ["00000000001001", "15420898376896"]
CODA_URL_MAP = {
"region-a": {
"CODA_AUTH_URL": "http://127.0.0.1:35357/v2.0/",
"NOVA_ADMIN_URL": "http://127.0.0.1:8774/v2",
"NEUTRON_ADMIN_URL": "http://127.0.0.1:9696/v2.0",
"CINDER_ADMIN_URL": "http://127.0.0.1:8776/v2",
"GLANCE_ADMIN_URL": "http://127.0.0.1:9292/v2",
},
# "region-b": {
# "CODA_AUTH_URL": "https://127.0.0.1:35357/v2.0/",
# "NOVA_ADMIN_URL": "https://127.0.0.1/v2",
# "NEUTRON_ADMIN_URL": "https://127.0.0.1/v2.0",
# "CINDER_ADMIN_URL": "https://127.0.0.1:8776/v1",
# "GLANCE_ADMIN_URL": "https://127.0.0.1:9292/v1.0",
# },
}

BIN
coda.pyc Normal file

Binary file not shown.

6
coda/__init__.py Normal file
View File

@ -0,0 +1,6 @@
"""The coda panel package.
Coda is a Horizon dashboard and panel (both share the name) that
facilitates resource clean up of a project once that project is no longer
needed http://openstack.org
"""

BIN
coda/__init__.pyc Normal file

Binary file not shown.

194
coda/cinder.py Normal file
View File

@ -0,0 +1,194 @@
# Copyright [2015] Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# 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.
"""
All cinder interactions go here.
Not much else to say.
"""
from django.conf import settings
import json
import requests
def list_volumes(auth_token, region, project_id):
"""List volumes."""
result = {}
headers = {
"X-Auth-Token": auth_token,
"Accept": "application/json",
}
params = {'all_tenants': '1', 'project_id': project_id}
if region in settings.CODA_URL_MAP:
response = requests.get(
"%s/%s/volumes/detail" % (
settings.CODA_URL_MAP[region][settings.CINDER_ADMIN_URL_KEY],
settings.CODA_TENANT_ID),
headers=headers, params=params, verify=False)
if response.status_code == 200:
volumes_dict = json.loads(response.text)
result = volumes_dict['volumes']
else:
result['error'] = \
repr(response.status_code) + " - " + response.text
else:
result['error'] = "Invalid region %s specified.", region
return result
def list_snapshots(auth_token, region, project_id):
"""List snapshots."""
result = {}
headers = {
"X-Auth-Token": auth_token,
"Accept": "application/json",
}
params = {'all_tenants': '1', 'project_id': project_id}
if region in settings.CODA_URL_MAP:
response = requests.get(
"%s/%s/snapshots/detail" % (
settings.CODA_URL_MAP[region][settings.CINDER_ADMIN_URL_KEY],
settings.CODA_TENANT_ID),
headers=headers, params=params, verify=False)
if response.status_code == 200:
snapshots_dict = json.loads(response.text)
result = snapshots_dict['snapshots']
else:
result['error'] = \
repr(response.status_code) + " - " + response.text
else:
result['error'] = "Invalid region %s specified.", region
return result
def list_backups(auth_token, region, project_id):
"""List backups."""
result = {}
headers = {
"X-Auth-Token": auth_token,
"Accept": "application/json"
}
# todo (nathan) figure out why this changed
# params = {'all_tenants': '1', 'project_id': project_id}
params = {}
if region in settings.CODA_URL_MAP:
response = requests.get(
"%s/%s/backups/detail" % (
settings.CODA_URL_MAP[region][settings.CINDER_ADMIN_URL_KEY],
settings.CODA_TENANT_ID),
headers=headers, params=params, verify=False)
if response.status_code == 200:
backups_dict = json.loads(response.text)
result = backups_dict['backups']
else:
result['error'] = \
repr(response.status_code) + " - " + response.text
else:
result['error'] = "Invalid region %s specified.", region
return result
def delete_volume(auth_token, region, tenant_id, volume_id):
"""Delete volumes."""
result = 'Volume Deleted'
if region in settings.CODA_URL_MAP:
headers = {
"X-Auth-Token": auth_token,
"Accept": "application/json",
}
params = {'all_tenants': '1', 'project_id': tenant_id}
# todo double check why I used codas tenant id here vs user tenant id
response = requests.delete("%s/%s/volumes/%s" % (
settings.CODA_URL_MAP[region][settings.CINDER_ADMIN_URL_KEY],
settings.CODA_TENANT_ID,
volume_id), headers=headers, params=params, verify=False)
if response.status_code != 202:
result = repr(response.status_code) + " - " + response.text
else:
result = "Invalid region %s specified.", region
return result
def delete_snapshot(auth_token, region, tenant_id, snapshot_id):
"""Delete snapshots."""
result = 'Snapshot Deleted'
if region in settings.CODA_URL_MAP:
headers = {
"X-Auth-Token": auth_token,
"Accept": "application/json",
}
params = {'all_tenants': '1', 'project_id': tenant_id}
# todo double check why I used codas tenant id here vs user tenant id
response = requests.delete("%s/%s/snapshots/%s" % (
settings.CODA_URL_MAP[region][settings.CINDER_ADMIN_URL_KEY],
settings.CODA_TENANT_ID,
snapshot_id), headers=headers, params=params, verify=False)
if response.status_code != 202:
result = repr(response.status_code) + " - " + response.text
else:
result = "Invalid region %s specified.", region
return result
def delete_backup(auth_token, region, tenant_id, backup_id):
"""Delete backups."""
result = 'Backup Deleted'
if region in settings.CODA_URL_MAP:
headers = {
"X-Auth-Token": auth_token,
"Accept": "application/json",
}
params = {'all_tenants': '1', 'project_id': tenant_id}
# todo double check why I used codas tenant id here vs user tenant id
response = requests.delete("%s/%s/backups/%s" % (
settings.CODA_URL_MAP[region][settings.CINDER_ADMIN_URL_KEY],
settings.CODA_TENANT_ID,
backup_id), headers=headers, params=params, verify=False)
if response.status_code != 202:
result = repr(response.status_code) + " - " + response.text
else:
result = "Invalid region %s specified.", region
return result

BIN
coda/cinder.pyc Normal file

Binary file not shown.

90
coda/coda.py Executable file
View File

@ -0,0 +1,90 @@
# Copyright [2015] Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# 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.
"""Code that is specific to Coda.
Methods that aren't specific to an OpenStack API go here.
"""
from django.conf import settings
from django.core.cache import cache
from openstack_dashboard.dashboards.coda.coda import keystone
CODA_CACHE = 'coda_cache'
def get_coda_regions():
"""Return a list of regions for the Coda installation."""
return settings.CODA_URL_MAP.keys()
def get_auth_token():
"""Abstract getting the Coda auth token from cache or API as needed."""
coda_cache = {}
if cache.get(CODA_CACHE) is not None:
coda_cache = cache.get(CODA_CACHE)
if 'coda_token' not in coda_cache:
coda_token = keystone.get_coda_token()
coda_cache['coda_token'] = coda_token
# keep for an hour
cache.set('coda_cache', coda_cache, 3600)
else:
coda_token = coda_cache['coda_token']
return coda_token
def fill_image_info(instances, images):
"""Use image dict to fill in image name for instances."""
for user_id in instances:
for instance in instances[user_id]:
if images is None:
instance['image']['name'] = "Image Info Unavailable"
else:
for image in images:
if image['id'] == instance['image']['id']:
instance['image']['name'] = image['name']
break
if 'name' not in instance['image']:
instance['image']['name'] = "Error Getting Image Info"
return instances
def fill_volume_info(volumes, snapshots, backups):
"""Unify volume, snapshot, and backup in a single dict."""
print backups
for volume in volumes:
volume['snapshots'] = []
volume['backups'] = []
for snapshot in snapshots:
if snapshot['volume_id'] == volume['id']:
volume['snapshots'].append(snapshot)
for backup in backups:
if backup['volume_id'] == volume['id']:
volume['backups'].append(backup)
return volumes
def is_project_black_listed(project_id):
"""Check if a project ID is blacklisted (i.e. shouldn't be cleaned)."""
return project_id in settings.CODA_BLACK_LIST

BIN
coda/coda.pyc Normal file

Binary file not shown.

163
coda/glance.py Normal file
View File

@ -0,0 +1,163 @@
# Copyright [2015] Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# 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.
"""
All glance interactions go here.
Not much else to say.
"""
from django.conf import settings
import json
import requests
def list_images(auth_token, region, tenant_id):
"""List all images for a project."""
result = {}
limit = 1000
headers = {
"X-Auth-Token": auth_token,
"Accept": "application/json",
}
params = {
'limit': limit,
'is_public': 'None',
'property-owner_id': tenant_id,
}
if region in settings.CODA_URL_MAP:
response = requests.get(
"%s/images/detail" %
settings.CODA_URL_MAP[region][settings.GLANCE_ADMIN_URL_KEY],
headers=headers,
params=params,
verify=False)
if response.status_code == 200:
images_dict = json.loads(response.text)
result = images_dict['images']
else:
result['error'] = \
repr(response.status_code) + " - " + response.text
else:
result['error'] = "Invalid region %s specified.", region
return result
def list_all_images(auth_token, region):
"""List all images for a region."""
result = {'public': []}
images = []
limit = 1000
headers = {
"X-Auth-Token": auth_token,
"Accept": "application/json",
}
params = {'limit': limit}
if region in settings.CODA_URL_MAP:
response = requests.get(
"%s/images" %
settings.CODA_URL_MAP[region][settings.GLANCE_ADMIN_URL_KEY],
headers=headers, params=params, verify=False)
if response.status_code == 200:
images_dict = json.loads(response.text)
count = len(images_dict['images'])
if count < limit:
images.extend(images_dict['images'])
else:
marker = images_dict['images'][count - 1]['id']
images = get_next_images(auth_token, region, limit,
images_dict['images'], marker)
else:
result['error'] = \
repr(response.status_code) + " - " + response.text
else:
result['error'] = "Invalid region %s specified." % region
for image in images:
if image['owner'] is None and image['is_public'] is True:
result['public'].append(image)
else:
if image['owner'] not in result:
result[image['owner']] = []
result[image['owner']].append(image)
return result
def get_next_images(auth_token, region, limit, images, marker):
"""Recursive method used to list all images for a region."""
headers = {
"X-Auth-Token": auth_token,
"Accept": "application/json",
}
params = {'limit': limit, 'is_public': 'None', 'marker': marker}
response = requests.get(
"%s/images/detail" %
settings.CODA_URL_MAP[region][settings.GLANCE_ADMIN_URL_KEY],
headers=headers,
params=params,
verify=False)
if response.status_code == 200:
images_dict = json.loads(response.text)
count = len(images_dict['images'])
if len(images_dict['images']) < limit:
images.extend(images_dict['images'])
else:
images.extend(images_dict['images'])
marker = images_dict['images'][count - 1]['id']
images = get_next_images(auth_token, region, limit, images, marker)
else:
# todo (nathan) throw exception here
print ("todo throw exception")
return images
def delete_image(auth_token, region, image_id):
"""Delete the image."""
result = 'Image Deleted'
if region in settings.CODA_URL_MAP:
headers = {
"X-Auth-Token": auth_token,
"Accept": "application/json",
}
response = requests.delete(
"%s/images/%s" % (
settings.CODA_URL_MAP[region][settings.GLANCE_ADMIN_URL_KEY],
image_id),
headers=headers,
verify=False)
if response.status_code != 204:
result = repr(response.status_code) + " - " + response.text
else:
result = "Invalid region %s specified.", region
return result

BIN
coda/glance.pyc Normal file

Binary file not shown.

117
coda/keystone.py Executable file
View File

@ -0,0 +1,117 @@
# Copyright [2015] Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# 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.
"""
All keystone interactions go here.
Not much else to say.
"""
from django.conf import settings
import json
import requests
JSON_HEADERS = {
"Content-Type": "application/json",
"Accept": "application/json",
}
def get_coda_token():
"""Return the auth token for the coda user."""
payload = {
"auth": {
"tenantId": settings.CODA_TENANT_ID,
"passwordCredentials": {
"username": settings.CODA_USERNAME,
"password": settings.CODA_PASSWORD
}
}
}
result = 'error'
try:
response = requests.post(
"%s/tokens" % settings.CODA_AUTH_URL,
data=json.dumps(payload),
headers=JSON_HEADERS,
verify=False)
result = json.loads(response.text)['access']['token']['id']
except Exception as ex:
print ("error in get_coda_token", ex)
return result
def get_project_users(auth_token, project_id):
"""Return a map of user info for a given project."""
headers = {
"X-Auth-Token": auth_token,
"Accept": "application/json",
}
response = requests.get(
"%s/tenants/%s/users" % (settings.CODA_KEYSTONE_URL, project_id),
headers=headers,
verify=False)
return json.loads(response.text)['users']
def user_authenticate(tenant_id, username, password):
"""Get the auth token for a user of Coda."""
payload = {
"auth": {
"tenantId": tenant_id,
"passwordCredentials": {
"username": username,
"password": password
}
}
}
result = 'error'
try:
response = requests.post("%s/tokens" % settings.CODA_AUTH_URL,
data=json.dumps(payload),
headers=JSON_HEADERS,
verify=False)
result = json.loads(response.text)['access']['token']['id']
except Exception as ex:
print ("error in user_authenticate", ex)
return result
def project_exists(auth_token, project_id):
"""Check if the project id is valid / exists.
Returns true with info if it does and false and empty if not.
"""
headers = {
"X-Auth-Token": auth_token,
"Accept": "application/json",
}
response = requests.get(
"%s/tenants/%s" % (settings.CODA_KEYSTONE_URL, project_id),
headers=headers,
verify=False)
if response.status_code == 200:
return True, json.loads(response.text)
else:
return False, ''

BIN
coda/keystone.pyc Normal file

Binary file not shown.

359
coda/neutron.py Normal file
View File

@ -0,0 +1,359 @@
# Copyright [2015] Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# 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.
"""
All keystone interactions go here.
Not much else to say.
"""
# import logging
# from django.core.cache import cache
from django.conf import settings
import json
import requests
def list_floating_ips(auth_token, region, project_id):
"""Return a list of maps with floating ip info."""
result = {}
if region in settings.CODA_URL_MAP:
headers = {
"X-Auth-Token": auth_token,
"Accept": "application/json",
}
params = {'all_tenants': '1', 'tenant_id': project_id}
col_filter = "fields=id&fields=floating_ip_address"
col_filter += "&fields=fixed_ip_address&fields=port_id"
response = requests.get(
"%s/floatingips?%s" % (
settings.CODA_URL_MAP[region][settings.NEUTRON_ADMIN_URL_KEY],
col_filter),
headers=headers, params=params, verify=False)
if response.status_code == 200:
floating_ip_dict = json.loads(response.text)
result = floating_ip_dict['floatingips']
else:
result['error'] = \
repr(response.status_code) + " - " + response.text
else:
result['error'] = "Invalid region %s specified.", region
return result
def list_security_groups(auth_token, region, project_id):
"""Return a list of maps with security group info."""
result = {}
headers = {
"X-Auth-Token": auth_token,
"Accept": "application/json",
}
params = {'all_tenants': '1', 'tenant_id': project_id}
if region in settings.CODA_URL_MAP:
response = requests.get(
"%s/security-groups" %
settings.CODA_URL_MAP[region][settings.NEUTRON_ADMIN_URL_KEY],
headers=headers,
params=params, verify=False)
if response.status_code == 200:
sec_group_dict = json.loads(response.text)
result = sec_group_dict['security_groups']
result = remove_default_security_group(result)
else:
result['error'] = \
repr(response.status_code) + " - " + response.text
else:
result['error'] = "Invalid region %s specified.", region
return result
def list_networks(auth_token, region, project_id):
"""List networks."""
result = {}
headers = {
"X-Auth-Token": auth_token,
"Accept": "application/json",
}
params = {'all_tenants': '1', 'tenant_id': project_id}
if region in settings.CODA_URL_MAP:
result[region] = {}
response = requests.get(
"%s/networks" %
settings.CODA_URL_MAP[region][settings.NEUTRON_ADMIN_URL_KEY],
headers=headers,
params=params,
verify=False)
if response.status_code == 200:
network_dict = json.loads(response.text)
result = network_dict['networks']
else:
result['error'] = \
repr(response.status_code) + " - " + response.text
else:
result['error'] = "Invalid region %s specified.", region
return result
def list_subnets(auth_token, region, project_id):
"""List subnets."""
result = {}
headers = {
"X-Auth-Token": auth_token,
"Accept": "application/json",
}
params = {'all_tenants': '1', 'tenant_id': project_id}
if region in settings.CODA_URL_MAP:
response = requests.get(
"%s/subnets" %
settings.CODA_URL_MAP[region][settings.NEUTRON_ADMIN_URL_KEY],
headers=headers,
params=params,
verify=False)
if response.status_code == 200:
subnet_dict = json.loads(response.text)
result = subnet_dict['subnets']
else:
result['error'] = \
repr(response.status_code) + " - " + response.text
else:
result['error'] = "Invalid region %s specified.", region
return result
def list_routers(auth_token, region, project_id):
"""List routers."""
result = {}
headers = {
"X-Auth-Token": auth_token,
"Accept": "application/json",
}
params = {'tenant_id': project_id}
if region in settings.CODA_URL_MAP:
response = requests.get(
"%s/routers" %
settings.CODA_URL_MAP[region][settings.NEUTRON_ADMIN_URL_KEY],
headers=headers,
params=params,
verify=False)
if response.status_code == 200:
router_dict = json.loads(response.text)
result = router_dict['routers']
else:
result['error'] = \
repr(response.status_code) + " - " + response.text
else:
result['error'] = "Invalid region %s specified.", region
return result
def delete_floating_ip(auth_token, region, tenant_id, floating_ip_id):
"""Delete floating ips."""
result = 'Floating IP Deleted'
if region in settings.CODA_URL_MAP:
headers = {
"X-Auth-Token": auth_token,
"Accept": "application/json",
}
params = {'all_tenants': '1', 'tenant_id': tenant_id, 'fields': 'id'}
response = requests.delete(
"%s/floatingips/%s" % (
settings.CODA_URL_MAP[region][settings.NEUTRON_ADMIN_URL_KEY],
floating_ip_id),
headers=headers,
params=params,
verify=False)
if response.status_code != 204:
result = repr(response.status_code) + " - " + response.text
else:
result = "Invalid region %s specified.", region
return result
def delete_security_group(auth_token, region, tenant_id, security_group_id):
"""Delete security groups."""
result = 'Security Group Deleted'
if region in settings.CODA_URL_MAP:
headers = {
"X-Auth-Token": auth_token,
"Accept": "application/json",
}
params = {'all_tenants': '1', 'tenant_id': tenant_id}
response = requests.delete(
"%s/security-groups/%s" % (
settings.CODA_URL_MAP[region][settings.NEUTRON_ADMIN_URL_KEY],
security_group_id),
headers=headers,
params=params,
verify=False)
if response.status_code != 204:
result = repr(response.status_code) + " - " + response.text
else:
result = "Invalid region %s specified.", region
return result
def delete_network(auth_token, region, tenant_id, network_id):
"""Delete networks."""
result = 'Network Deleted'
if region in settings.CODA_URL_MAP:
headers = {
"X-Auth-Token": auth_token,
"Accept": "application/json",
}
params = {'all_tenants': '1', 'tenant_id': tenant_id}
response = requests.delete(
"%s/networks/%s" % (
settings.CODA_URL_MAP[region][settings.NEUTRON_ADMIN_URL_KEY],
network_id),
headers=headers,
params=params,
verify=False)
if response.status_code != 204:
result = repr(response.status_code) + " - " + response.text
else:
result = "Invalid region %s specified.", region
return result
def delete_router(auth_token, region, tenant_id, router_id):
"""Delete routers."""
result = 'Router Deleted'
if region in settings.CODA_URL_MAP:
headers = {
"X-Auth-Token": auth_token,
"Accept": "application/json",
}
ports = list_ports_for_device(
auth_token,
settings.CODA_URL_MAP[region][settings.NEUTRON_ADMIN_URL_KEY],
tenant_id,
router_id)
port_error = False
for port in ports:
data = {'port_id': port['id']}
response = requests.put(
"%s/routers/%s/remove_router_interface" %
(settings.CODA_URL_MAP[region][settings.NEUTRON_ADMIN_URL_KEY],
router_id),
headers=headers,
data=json.dumps(data),
verify=False)
if response.status_code != 200:
result = "Couldn't remove port id [%s]. Delete aborted." \
% port['id']
port_error = True
break
# Remove the router if the interfaces were cleared ok.
if not port_error:
params = {'all_tenants': '1', 'tenant_id': tenant_id}
response = requests.delete(
"%s/routers/%s" %
(settings.CODA_URL_MAP[region][settings.NEUTRON_ADMIN_URL_KEY],
router_id),
headers=headers,
params=params,
verify=False)
if response.status_code != 204:
result = repr(response.status_code) + " - " + response.text
else:
result = "Invalid region %s specified.", region
return result
def list_ports_for_device(auth_token, url_base, tenant_id, device_id):
"""Utility method used by delete."""
result = {}
headers = {
"X-Auth-Token": auth_token,
"Accept": "application/json",
}
params = {
'all_tenants': '1',
'tenant_id': tenant_id,
'device_id': device_id,
}
response = requests.get(
"%s/ports" % url_base,
headers=headers,
params=params,
verify=False)
if response.status_code == 200:
ports_dict = json.loads(response.text)
result = ports_dict['ports']
return result
def remove_default_security_group(security_groups):
"""Utility method to prevent default sec group from displaying."""
# todo (nathan) awful, use a list comprehension for this
result = []
for group in security_groups:
if group['name'] != 'default':
result.append(group)
return result

BIN
coda/neutron.pyc Normal file

Binary file not shown.

95
coda/nova.py Normal file
View File

@ -0,0 +1,95 @@
# Copyright [2015] Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# 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.
"""
All Nova interactions go here.
Not much else to say.
"""
# import logging
# from django.core.cache import cache
from django.conf import settings
import json
import requests
def list_instances(auth_token, region, tenant_id):
"""Return a map keyed with user ids to a map with instance info."""
result = {}
headers = {
"X-Auth-Token": auth_token,
"Accept": "application/json",
}
payload = {'all_tenants': '1', 'tenant_id': tenant_id}
if region in settings.CODA_URL_MAP:
response = requests.get(
"%s/%s/servers/detail" % (
settings.CODA_URL_MAP[region][settings.NOVA_ADMIN_URL_KEY],
settings.CODA_TENANT_ID),
headers=headers,
params=payload,
verify=False)
if response.status_code == 200:
server_dict = json.loads(response.text)
if "servers" in server_dict:
sorted_instances = \
sorted(server_dict['servers'], key=lambda k: k['user_id'])
for instance in sorted_instances:
if instance['user_id'] not in result.keys():
result[instance['user_id']] = []
result[instance['user_id']].append(instance)
else:
result['error'] = \
repr(response.status_code) + " - " + response.text
else:
result['error'] = "Invalid region %s specified.", region
return result
def delete_instance(auth_token, region, tenant_id, instance_id):
"""Delete the instance."""
result = 'Instance Deleted'
if region in settings.CODA_URL_MAP:
headers = {
"X-Auth-Token": auth_token,
"Accept": "application/json",
}
payload = {'all_tenants': '1', 'tenant_id': tenant_id}
response = requests.delete(
"%s/%s/servers/%s" % (
settings.CODA_URL_MAP[region][settings.NOVA_ADMIN_URL_KEY],
settings.CODA_TENANT_ID,
instance_id),
headers=headers,
params=payload,
verify=False)
if response.status_code != 204:
result = repr(response.status_code) + " - " + response.text
else:
result = "Invalid region %s specified.", region
return result

BIN
coda/nova.pyc Normal file

Binary file not shown.

35
coda/panel.py Normal file
View File

@ -0,0 +1,35 @@
# Copyright [2015] Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# 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.
"""
The Coda panel definition.
Not much else to say.
"""
from django.utils.translation import ugettext_lazy as _
import horizon
from openstack_dashboard.dashboards.coda import dashboard
class Coda(horizon.Panel):
"""The Coda panel class."""
name = _("Coda")
slug = "coda"
dashboard.Coda.register(Coda)

BIN
coda/panel.pyc Normal file

Binary file not shown.

View File

@ -0,0 +1,27 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Coda - Resource Cleanup" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Coda - Resource Cleanup") %}
{% endblock page_header %}
{% block main %}
<div>
<form id="coda_form" action="{% url 'horizon:coda:coda:results' %}" method="post" style="width: 655px">
{% csrf_token %}
<label for="project_id">Project ID:</label>
<input id="project_id" type="text" name="project_id" style="width: 400px" value="{% if project_id %}{{ project_id }}{%endif%}" autofocus="true" tabindex="10"/>
<input class="btn btn-primary" type="submit" value="Search" tabindex="20"/>
<input id="update_cache" type="checkbox" name="update_cache" value="True" tabindex="20" {% ifequal update_cache 'True' %}checked{%endifequal%}/> Update Cache
</form>
</div>
{% block results %}{% endblock %}
{% endblock %}
{% block js %}
{{ block.super }}
<script type="text/javascript" src="{{ STATIC_URL }}/coda/js/js.cookie.js"></script>
{% block custom_js %}{% endblock %}
{% endblock %}

View File

@ -0,0 +1,97 @@
{% extends "coda/coda/coda_base.html" %}
{% load staticfiles %}
{% block title %}Confirm Delete?{% endblock %}
{% block custom_js %}
<script type="text/javascript">
function confirmDelete() {
if (confirm("Are you sure you want to delete all resources?")) {
document.scrubber_form.submit();
} else {
return false;
}
}
</script>
{% endblock %}
{% block results %}
<div id="splash">
<div class="sidebar">
<h2>Project Info</h2>
<p>Project Name: {{ project_info.tenant.name }}</p>
<p>Project ID: {{ project_info.tenant.id}}</p>
<p>Enabled: {{ project_info.tenant.enabled }}</p>
<p>Description: {{ project_info.tenant.description}}</p>
<input type="hidden" name="project_id" value="{{ project_id }}">
{% if users %}
<h4>Users:</h4>
{% for user in users %}
<p>Name: {{ user.name }}</p>
<p>Username: {{ user.username }}</p>
<p>ID: {{ user.id }}</p>
<p>Enabled: {{ user.enabled }}</p>
<p>E-Mail: {{ user.email }}</p>
{% endfor %}
{% else %}
<h4>No Users in this Project</h4>
{% endif %}
</div>
<div id='content_body'>
<div class="warning">
<h2 class="warning-text">WARNING!</h2>
<h3 class="warning-text"> You are about to delete all resources this project. This action cannot be undone.</h3>
</div>
<div class="row large-rounded">
<div id="login" class="confirm">
<div class="modal-header">
<h3>Enter Admin Credentials to Continue</h3>
</div>
<form id="delete_form" action="{% url 'horizon:coda:coda:delete_resources' %}" method="post">
{% csrf_token %}
<div class="modal-body clearfix">
<div class="messages"></div>
<fieldset>
<input type="hidden" name="domainId" value="{{ domain_id }}">
<div class="control-group form-field clearfix ">
<label for="os_username">OS_USERNAME</label>
<div class="input">
<input autofocus="true" id="os_username" name="os_username" tabindex="10" type="text">
</div>
</div>
<div class="control-group form-field clearfix ">
<label for="os_password">OS_PASSWORD</label>
<div class="input">
<input id="os_password" name="os_password" tabindex="20" type="password">
</div>
</div>
<div class="control-group form-field clearfix ">
<label for="os_tenant_id">OS_TENANT_ID</label>
<div class="input">
<input id="os_tenant_id" name="os_tenant_id" tabindex="30" type="text">
</div>
</div>
<input type="hidden" name="project_id" value="{{ project_id }}">
</fieldset>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary pull-right" tabindex="40">Delete Resources</button>
<a href="{% url 'horizon:coda:coda:index' %}">
<input class="btn pull-right" type="button" value="Cancel" />
</a>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,78 @@
{% load template_utils %}
<script type="text/javascript">
var backups_{{ region|jsfunc }}_{{ project_id }}_done = false;
$(document).ready(function(){
var csrftoken = Cookies.get('csrftoken');
var project_id = "{{ project_id }}";
var region = "{{ region }}";
var user_token = "{{ user_token }}";
$.post('/coda/delete/backups/',
{csrfmiddlewaretoken: csrftoken, project_id: project_id, region: region, user_token: user_token},
function(data, status) {
var resultsDiv = $('#backups_{{ region }}_{{ project_id }}').find('#results');
data = JSON.parse(data);
if(Object.keys(data).length > 0) {
var tableWrapper = $("<div>").addClass("table_wrapper");
tableWrapper.append("<h3 class=\"table_title\">Backups</h3>");
var table = $("<table>");
table.addClass("table")
table.addClass("table-bordered")
table.addClass("datatable")
var thead = $("<thead>");
var tr = $("<tr>");
tr.append($("<th>UUID</th>").addClass("normal_column"));
tr.append($("<th>Outcome</th>").addClass("normal_column"));
thead.append(tr);
table.append(thead);
var tbody = $("<tbody>");
$.map(data, function (outcome, instanceId) {
var tr = $("<tr>");
tr.append("<td>" + instanceId + "</td>");
tr.append("<td>" + outcome + "</td>");
tbody.append(tr);
});
table.append(tbody);
var tfoot = $("<tfoot>");
var tr = $("<tr>");
var td = $("<td colspan='2'>");
var span = $("<span colspan='2'>");
var footerMessage = "Displaying " + Object.keys(data).length + " item";
if (Object.keys(data).length > 1)
footerMessage += "s";
span.append(footerMessage);
td.append(span);
tr.append(td);
tfoot.append(tr);
table.append(tfoot);
tableWrapper.append(table);
resultsDiv.append(tableWrapper);
} else {
var resultsDiv = $('#backups_{{ region }}_{{ project_id }}').find('#results');
resultsDiv.html("<h4>No Backups in region.</h4>")
}
$('#backups_{{ region }}_{{ project_id }}').find('#deleting').hide();
$('#backups_{{ region }}_{{ project_id }}').find('#results').show();
backups_{{ region|jsfunc }}_{{ project_id }}_done = true;
scrub_volumes_{{ region|jsfunc }}_{{ project_id }}();
});
});
</script>
<div id="backups_{{ region }}_{{ project_id }}" class="table_wrapper">
<div id="deleting">
<h3>Deleting Backups...</h3>
</div>
<div id="results" style="display: none">
</div>
</div>

View File

@ -0,0 +1,78 @@
{% load template_utils %}
<script type="text/javascript">
function scrub_floating_ips_{{ region|jsfunc }}_{{ project_id }}(){
var csrftoken = Cookies.get('csrftoken');
var project_id = "{{ project_id }}";
var region = "{{ region }}";
var user_token = "{{ user_token }}";
$('#floating_ips_{{ region }}_{{ project_id }}').find('#deleting').find('h3').html("Deleting floating IPs...")
$.post('/coda/delete/floating_ips/',
{csrfmiddlewaretoken: csrftoken, project_id: project_id, region: region, user_token: user_token},
function(data, status) {
var resultsDiv = $('#floating_ips_{{ region }}_{{ project_id }}').find('#results');
data = JSON.parse(data);
if(Object.keys(data).length > 0) {
var tableWrapper = $("<div>").addClass("table_wrapper");
tableWrapper.append("<h3 class=\"table_title\">Floating IPs</h3>");
var table = $("<table>");
table.addClass("table")
table.addClass("table-bordered")
table.addClass("datatable")
var thead = $("<thead>");
var tr = $("<tr>");
tr.append($("<th>UUID</th>").addClass("normal_column"));
tr.append($("<th>Outcome</th>").addClass("normal_column"));
thead.append(tr);
table.append(thead);
var tbody = $("<tbody>");
$.map(data, function (outcome, instanceId) {
var tr = $("<tr>");
tr.append("<td>" + instanceId + "</td>");
tr.append("<td>" + outcome + "</td>");
tbody.append(tr);
});
table.append(tbody);
var tfoot = $("<tfoot>");
var tr = $("<tr>");
var td = $("<td colspan='2'>");
var span = $("<span colspan='2'>");
var footerMessage = "Displaying " + Object.keys(data).length + " item";
if (Object.keys(data).length > 1)
footerMessage += "s";
span.append(footerMessage);
td.append(span);
tr.append(td);
tfoot.append(tr);
table.append(tfoot);
tableWrapper.append(table);
resultsDiv.append(tableWrapper);
} else {
var resultsDiv = $('#floating_ips_{{ region }}_{{ project_id }}').find('#results');
resultsDiv.html("<h4>No Floating IPs in region.</h4>")
}
$('#floating_ips_{{ region }}_{{ project_id }}').find('#deleting').hide();
$('#floating_ips_{{ region }}_{{ project_id }}').find('#results').show();
wait_security_groups_{{ region|jsfunc }}_{{ project_id }}();
});
}
</script>
<div id="floating_ips_{{ region }}_{{ project_id }}" class="table_wrapper">
<div id="deleting">
<h3>Waiting for instances to delete...</h3>
</div>
<div id="results" style="display: none">
</div>
</div>

View File

@ -0,0 +1,74 @@
<script type="text/javascript">
$(document).ready(function(){
var csrftoken = Cookies.get('csrftoken');
var project_id = "{{ project_id }}";
var region = "{{ region }}";
var user_token = "{{ user_token }}";
$.post('/coda/delete/images/',
{csrfmiddlewaretoken: csrftoken, project_id: project_id, region: region, user_token: user_token},
function(data, status) {
var resultsDiv = $('#images_{{ region }}_{{ project_id }}').find('#results');
data = JSON.parse(data);
if(Object.keys(data).length > 0) {
var tableWrapper = $("<div>").addClass("table_wrapper");
tableWrapper.append("<h3 class=\"table_title\">Images</h3>");
var table = $("<table>");
table.addClass("table")
table.addClass("table-bordered")
table.addClass("datatable")
var thead = $("<thead>");
var tr = $("<tr>");
tr.append($("<th>UUID</th>").addClass("normal_column"));
tr.append($("<th>Outcome</th>").addClass("normal_column"));
thead.append(tr);
table.append(thead);
var tbody = $("<tbody>");
$.map(data, function (outcome, instanceId) {
var tr = $("<tr>");
tr.append("<td>" + instanceId + "</td>");
tr.append("<td>" + outcome + "</td>");
tbody.append(tr);
});
table.append(tbody);
var tfoot = $("<tfoot>");
var tr = $("<tr>");
var td = $("<td colspan='2'>");
var span = $("<span colspan='2'>");
var footerMessage = "Displaying " + Object.keys(data).length + " item";
if (Object.keys(data).length > 1)
footerMessage += "s";
span.append(footerMessage);
td.append(span);
tr.append(td);
tfoot.append(tr);
table.append(tfoot);
tableWrapper.append(table);
resultsDiv.append(tableWrapper);
} else {
var resultsDiv = $('#images_{{ region }}_{{ project_id }}').find('#results');
resultsDiv.html("<h4>No Images in region.</h4>")
}
$('#images_{{ region }}_{{ project_id }}').find('#deleting').hide();
$('#images_{{ region }}_{{ project_id }}').find('#results').show();
});
});
</script>
<div id="images_{{ region }}_{{ project_id }}" class="table_wrapper">
<div id="deleting">
<h3>Deleting Images...</h3>
</div>
<div id="results" style="display: none">
</div>
</div>

View File

@ -0,0 +1,76 @@
{% load template_utils %}
<script type="text/javascript">
$(document).ready(function(){
var csrftoken = Cookies.get('csrftoken');
var project_id = "{{ project_id }}";
var region = "{{ region }}";
var user_token = "{{ user_token }}";
$.post('/coda/delete/instances/',
{csrfmiddlewaretoken: csrftoken, project_id: project_id, region: region, user_token: user_token},
function(data, status) {
var resultsDiv = $('#instances_{{ region }}_{{ project_id }}').find('#results');
data = JSON.parse(data);
if(Object.keys(data).length > 0) {
var tableWrapper = $("<div>").addClass("table_wrapper");
tableWrapper.append("<h3 class=\"table_title\">Instances</h3>");
var table = $("<table>");
table.addClass("table")
table.addClass("table-bordered")
table.addClass("datatable")
var thead = $("<thead>");
var tr = $("<tr>");
tr.append($("<th>UUID</th>").addClass("normal_column"));
tr.append($("<th>Outcome</th>").addClass("normal_column"));
thead.append(tr);
table.append(thead);
var tbody = $("<tbody>");
$.map(data, function (outcome, instanceId) {
var tr = $("<tr>");
tr.append("<td>" + instanceId + "</td>");
tr.append("<td>" + outcome + "</td>");
tbody.append(tr);
});
table.append(tbody);
var tfoot = $("<tfoot>");
var tr = $("<tr>");
var td = $("<td colspan='2'>");
var span = $("<span colspan='2'>");
var footerMessage = "Displaying " + Object.keys(data).length + " item";
if (Object.keys(data).length > 1)
footerMessage += "s";
span.append(footerMessage);
td.append(span);
tr.append(td);
tfoot.append(tr);
table.append(tfoot);
tableWrapper.append(table);
resultsDiv.append(tableWrapper);
} else {
var resultsDiv = $('#instances_{{ region }}_{{ project_id }}').find('#results');
resultsDiv.html("<h4>No Instances in region.</h4>")
}
$('#instances_{{ region }}_{{ project_id }}').find('#deleting').hide();
$('#instances_{{ region }}_{{ project_id }}').find('#results').show();
scrub_floating_ips_{{ region|jsfunc }}_{{ project_id }}();
});
});
</script>
<div id="instances_{{ region }}_{{ project_id }}" class="table_wrapper">
<div id="deleting">
<h3>Deleting instances...</h3>
</div>
<div id="results" style="display: none">
</div>
</div>

View File

@ -0,0 +1,77 @@
{% load template_utils %}
<script type="text/javascript">
function scrub_networks_{{ region|jsfunc }}_{{ project_id }}(){
var csrftoken = Cookies.get('csrftoken');
var project_id = "{{ project_id }}";
var region = "{{ region }}";
var user_token = "{{ user_token }}";
$('#networks_{{ region }}_{{ project_id }}').find('#deleting').find('h3').html("Deleting Networks...")
$.post('/coda/delete/networks/',
{csrfmiddlewaretoken: csrftoken, project_id: project_id, region: region, user_token: user_token},
function(data, status) {
var resultsDiv = $('#networks_{{ region }}_{{ project_id }}').find('#results');
data = JSON.parse(data);
if(Object.keys(data).length > 0) {
var tableWrapper = $("<div>").addClass("table_wrapper");
tableWrapper.append("<h3 class=\"table_title\">Networks</h3>");
var table = $("<table>");
table.addClass("table")
table.addClass("table-bordered")
table.addClass("datatable")
var thead = $("<thead>");
var tr = $("<tr>");
tr.append($("<th>UUID</th>").addClass("normal_column"));
tr.append($("<th>Outcome</th>").addClass("normal_column"));
thead.append(tr);
table.append(thead);
var tbody = $("<tbody>");
$.map(data, function (outcome, instanceId) {
var tr = $("<tr>");
tr.append("<td>" + instanceId + "</td>");
tr.append("<td>" + outcome + "</td>");
tbody.append(tr);
});
table.append(tbody);
var tfoot = $("<tfoot>");
var tr = $("<tr>");
var td = $("<td colspan='2'>");
var span = $("<span colspan='2'>");
var footerMessage = "Displaying " + Object.keys(data).length + " item";
if (Object.keys(data).length > 1)
footerMessage += "s";
span.append(footerMessage);
td.append(span);
tr.append(td);
tfoot.append(tr);
table.append(tfoot);
tableWrapper.append(table);
resultsDiv.append(tableWrapper);
} else {
var resultsDiv = $('#networks_{{ region }}_{{ project_id }}').find('#results');
resultsDiv.html("<h4>No Networks in region.</h4>")
}
$('#networks_{{ region }}_{{ project_id }}').find('#deleting').hide();
$('#networks_{{ region }}_{{ project_id }}').find('#results').show();
});
}
</script>
<div id="networks_{{ region }}_{{ project_id }}" class="table_wrapper">
<div id="deleting">
<h3>Waiting for Routers to delete...</h3>
</div>
<div id="results" style="display: none">
</div>
</div>

View File

@ -0,0 +1,51 @@
{% extends "coda/coda/coda_base.html" %}
{% load staticfiles %}
{% block title %}Delete Results{% endblock %}
{% block results %}
<div id="main_content">
<div class="sidebar">
<h2>Project Info</h2>
<div style="margin-left: 14px">
<p>Project Name: {{ project_info.tenant.name }}</p>
<p>Project ID: {{ project_info.tenant.id}}</p>
<p>Enabled: {{ project_info.tenant.enabled }}</p>
<p>Description: {{ project_info.tenant.description}}</p>
<input type="hidden" name="project_id" value="{{ project_id }}">
{% if users %}
<h4>Users:</h4>
{% for user in users %}
<p>Name: {{ user.name }}</p>
<p>Username: {{ user.username }}</p>
<p>ID: {{ user.id }}</p>
<p>Enabled: {{ user.enabled }}</p>
<p>E-Mail: {{ user.email }}</p>
{% endfor %}
{% else %}
<h4>No Users in this Project</h4>
{% endif %}
</div>
</div>
<div id='content_body'>
<h1>Coda Results for Project ID: {{ project_id }}</h1>
{% for region in regions %}
<h2>Region: {{ region }}</h2>
{% include "coda/coda/delete/instances.html" %}
{% include "coda/coda/delete/floating_ips.html" %}
{% include "coda/coda/delete/security_groups.html" %}
{% include "coda/coda/delete/routers.html" %}
{% include "coda/coda/delete/networks.html" %}
{% include "coda/coda/delete/snapshots.html" %}
{% include "coda/coda/delete/backups.html" %}
{% include "coda/coda/delete/volumes.html" %}
{% include "coda/coda/delete/images.html" %}
{% endfor %} <!-- END region for loop -->
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,78 @@
{% load template_utils %}
<script type="text/javascript">
function scrub_routers_{{ region|jsfunc }}_{{ project_id }}(){
var csrftoken = Cookies.get('csrftoken');
var project_id = "{{ project_id }}";
var region = "{{ region }}";
var user_token = "{{ user_token }}";
$('#routers_{{ region }}_{{ project_id }}').find('#deleting').find('h3').html("Deleting Routers...")
$.post('/coda/delete/routers/',
{csrfmiddlewaretoken: csrftoken, project_id: project_id, region: region, user_token: user_token},
function(data, status) {
var resultsDiv = $('#routers_{{ region }}_{{ project_id }}').find('#results');
data = JSON.parse(data);
if(Object.keys(data).length > 0) {
var tableWrapper = $("<div>").addClass("table_wrapper");
tableWrapper.append("<h3 class=\"table_title\">Routers</h3>");
var table = $("<table>");
table.addClass("table")
table.addClass("table-bordered")
table.addClass("datatable")
var thead = $("<thead>");
var tr = $("<tr>");
tr.append($("<th>UUID</th>").addClass("normal_column"));
tr.append($("<th>Outcome</th>").addClass("normal_column"));
thead.append(tr);
table.append(thead);
var tbody = $("<tbody>");
$.map(data, function (outcome, instanceId) {
var tr = $("<tr>");
tr.append("<td>" + instanceId + "</td>");
tr.append("<td>" + outcome + "</td>");
tbody.append(tr);
});
table.append(tbody);
var tfoot = $("<tfoot>");
var tr = $("<tr>");
var td = $("<td colspan='2'>");
var span = $("<span colspan='2'>");
var footerMessage = "Displaying " + Object.keys(data).length + " item";
if (Object.keys(data).length > 1)
footerMessage += "s";
span.append(footerMessage);
td.append(span);
tr.append(td);
tfoot.append(tr);
table.append(tfoot);
tableWrapper.append(table);
resultsDiv.append(tableWrapper);
} else {
var resultsDiv = $('#routers_{{ region }}_{{ project_id }}').find('#results');
resultsDiv.html("<h4>No Routers in region.</h4>")
}
$('#routers_{{ region }}_{{ project_id }}').find('#deleting').hide();
$('#routers_{{ region }}_{{ project_id }}').find('#results').show();
scrub_networks_{{ region|jsfunc }}_{{ project_id }}();
});
}
</script>
<div id="routers_{{ region }}_{{ project_id }}" class="table_wrapper">
<div id="deleting">
<h3>Waiting for Security Groups to delete...</h3>
</div>
<div id="results" style="display: none">
</div>
</div>

View File

@ -0,0 +1,83 @@
{% load template_utils %}
<script type="text/javascript">
function wait_security_groups_{{ region|jsfunc }}_{{ project_id }}(){
$('#security_groups_{{ region }}_{{ project_id }}').find('#deleting').find('h3').html("Floating IP's deleted. Waiting a few seconds for ports to clear...")
setTimeout(function() { scrub_security_groups_{{ region|jsfunc }}_{{ project_id }}() }, 5000);
}
function scrub_security_groups_{{ region|jsfunc }}_{{ project_id }}(){
var csrftoken = Cookies.get('csrftoken');
var project_id = "{{ project_id }}";
var region = "{{ region }}";
var user_token = "{{ user_token }}";
$('#security_groups_{{ region }}_{{ project_id }}').find('#deleting').find('h3').html("Deleting security groups...")
$.post('/coda/delete/security_groups/',
{csrfmiddlewaretoken: csrftoken, project_id: project_id, region: region, user_token: user_token},
function(data, status) {
var resultsDiv = $('#security_groups_{{ region }}_{{ project_id }}').find('#results');
data = JSON.parse(data);
if(Object.keys(data).length > 0) {
var tableWrapper = $("<div>").addClass("table_wrapper");
tableWrapper.append("<h3 class=\"table_title\">Security Groups</h3>");
var table = $("<table>");
table.addClass("table")
table.addClass("table-bordered")
table.addClass("datatable")
var thead = $("<thead>");
var tr = $("<tr>");
tr.append($("<th>UUID</th>").addClass("normal_column"));
tr.append($("<th>Outcome</th>").addClass("normal_column"));
thead.append(tr);
table.append(thead);
var tbody = $("<tbody>");
$.map(data, function (outcome, instanceId) {
var tr = $("<tr>");
tr.append("<td>" + instanceId + "</td>");
tr.append("<td>" + outcome + "</td>");
tbody.append(tr);
});
table.append(tbody);
var tfoot = $("<tfoot>");
var tr = $("<tr>");
var td = $("<td colspan='2'>");
var span = $("<span colspan='2'>");
var footerMessage = "Displaying " + Object.keys(data).length + " item";
if (Object.keys(data).length > 1)
footerMessage += "s";
span.append(footerMessage);
td.append(span);
tr.append(td);
tfoot.append(tr);
table.append(tfoot);
tableWrapper.append(table);
resultsDiv.append(tableWrapper);
} else {
var resultsDiv = $('#security_groups_{{ region }}_{{ project_id }}').find('#results');
resultsDiv.html("<h4>No Security Groups in region.</h4>")
}
$('#security_groups_{{ region }}_{{ project_id }}').find('#deleting').hide();
$('#security_groups_{{ region }}_{{ project_id }}').find('#results').show();
scrub_routers_{{ region|jsfunc }}_{{ project_id }}();
});
}
</script>
<div id="security_groups_{{ region }}_{{ project_id }}" class="table_wrapper">
<div id="deleting">
<h3>Waiting for Floating IPs to delete...</h3>
</div>
<div id="results" style="display: none">
</div>
</div>

View File

@ -0,0 +1,79 @@
{% load template_utils %}
<script type="text/javascript">
var snapshots_{{ region|jsfunc }}_{{ project_id }}_done = false;
$(document).ready(function(){
var csrftoken = Cookies.get('csrftoken');
var project_id = "{{ project_id }}";
var region = "{{ region }}";
var user_token = "{{ user_token }}";
$.post('/coda/delete/snapshots/',
{csrfmiddlewaretoken: csrftoken, project_id: project_id, region: region, user_token: user_token},
function(data, status) {
var resultsDiv = $('#snapshots_{{ region }}_{{ project_id }}').find('#results');
data = JSON.parse(data);
if(Object.keys(data).length > 0) {
var tableWrapper = $("<div>").addClass("table_wrapper");
tableWrapper.append("<h3 class=\"table_title\">Snapshots</h3>");
var table = $("<table>");
table.addClass("table")
table.addClass("table-bordered")
table.addClass("datatable")
var thead = $("<thead>");
var tr = $("<tr>");
tr.append($("<th>UUID</th>").addClass("normal_column"));
tr.append($("<th>Outcome</th>").addClass("normal_column"));
thead.append(tr);
table.append(thead);
var tbody = $("<tbody>");
$.map(data, function (outcome, instanceId) {
var tr = $("<tr>");
tr.append("<td>" + instanceId + "</td>");
tr.append("<td>" + outcome + "</td>");
tbody.append(tr);
});
table.append(tbody);
var tfoot = $("<tfoot>");
var tr = $("<tr>");
var td = $("<td colspan='2'>");
var span = $("<span colspan='2'>");
var footerMessage = "Displaying " + Object.keys(data).length + " item";
if (Object.keys(data).length > 1)
footerMessage += "s";
span.append(footerMessage);
td.append(span);
tr.append(td);
tfoot.append(tr);
table.append(tfoot);
tableWrapper.append(table);
resultsDiv.append(tableWrapper);
} else {
var resultsDiv = $('#snapshots_{{ region }}_{{ project_id }}').find('#results');
resultsDiv.html("<h4>No Snapshots in region.</h4>")
}
$('#snapshots_{{ region }}_{{ project_id }}').find('#deleting').hide();
$('#snapshots_{{ region }}_{{ project_id }}').find('#results').show();
snapshots_{{ region|jsfunc }}_{{ project_id }}_done = true;
scrub_volumes_{{ region|jsfunc }}_{{ project_id }}();
});
});
</script>
<div id="snapshots_{{ region }}_{{ project_id }}" class="table_wrapper">
<div id="deleting">
<h3>Deleting Snapshots...</h3>
</div>
<div id="results" style="display: none">
</div>
</div>

View File

@ -0,0 +1,87 @@
{% load template_utils %}
<script type="text/javascript">
function scrub_volumes_{{ region|jsfunc }}_{{ project_id }}(){
var csrftoken = Cookies.get('csrftoken');
var project_id = "{{ project_id }}";
var region = "{{ region }}";
var user_token = "{{ user_token }}";
if (!backups_{{ region|jsfunc }}_{{ project_id }}_done) {
$('#volumes_{{ region }}_{{ project_id }}').find('#deleting').find('h3').html("Waiting for Backups to delete...")
return;
}
if (!snapshots_{{ region|jsfunc }}_{{ project_id }}_done) {
$('#volumes_{{ region }}_{{ project_id }}').find('#deleting').find('h3').html("Waiting for Snapshots to delete...")
return;
}
$('#volumes_{{ region }}_{{ project_id }}').find('#deleting').find('h3').html("Deleting Volumes...")
$.post('/coda/delete/volumes/',
{csrfmiddlewaretoken: csrftoken, project_id: project_id, region: region, user_token: user_token},
function(data, status) {
var resultsDiv = $('#volumes_{{ region }}_{{ project_id }}').find('#results');
data = JSON.parse(data);
if(Object.keys(data).length > 0) {
var tableWrapper = $("<div>").addClass("table_wrapper");
tableWrapper.append("<h3 class=\"table_title\">Volumes</h3>");
var table = $("<table>");
table.addClass("table")
table.addClass("table-bordered")
table.addClass("datatable")
var thead = $("<thead>");
var tr = $("<tr>");
tr.append($("<th>UUID</th>").addClass("normal_column"));
tr.append($("<th>Outcome</th>").addClass("normal_column"));
thead.append(tr);
table.append(thead);
var tbody = $("<tbody>");
$.map(data, function (outcome, instanceId) {
var tr = $("<tr>");
tr.append("<td>" + instanceId + "</td>");
tr.append("<td>" + outcome + "</td>");
tbody.append(tr);
});
table.append(tbody);
var tfoot = $("<tfoot>");
var tr = $("<tr>");
var td = $("<td colspan='2'>");
var span = $("<span colspan='2'>");
var footerMessage = "Displaying " + Object.keys(data).length + " item";
if (Object.keys(data).length > 1)
footerMessage += "s";
span.append(footerMessage);
td.append(span);
tr.append(td);
tfoot.append(tr);
table.append(tfoot);
tableWrapper.append(table);
resultsDiv.append(tableWrapper);
} else {
var resultsDiv = $('#volumes_{{ region }}_{{ project_id }}').find('#results');
resultsDiv.html("<h4>No Volumes in region.</h4>")
}
$('#volumes_{{ region }}_{{ project_id }}').find('#deleting').hide();
$('#volumes_{{ region }}_{{ project_id }}').find('#results').show();
});
}
</script>
<div id="volumes_{{ region }}_{{ project_id }}" class="table_wrapper">
<div id="deleting">
<h3>Waiting for Backups and Snapshots to delete...</h3>
</div>
<div id="results" style="display: none">
</div>
</div>

View File

@ -0,0 +1,11 @@
{% extends "coda/coda/coda_base.html" %}
{% load staticfiles %}
{% block title %}Error{% endblock %}
{% block results %}
<div>
<h1 style="color: red; text-align: center">Error!</h1>
<h2 style="color: red; text-align: center">{{ error_message }}</h2>
</div>
{% endblock %}

View File

@ -0,0 +1,65 @@
<script type="text/javascript">
$(document).ready(function(){
var csrftoken = Cookies.get('csrftoken');
var project_id = "{{ project_id }}";
var region = "{{ region }}";
var update_cache = "{{ update_cache }}";
$.post('/coda/floating_ips/', {csrfmiddlewaretoken: csrftoken, project_id: project_id, region: region, update_cache: update_cache}, function(data, status){
var tbody = $('#floatingips_{{ region }}_{{ project_id }}').find('tbody');
data = JSON.parse(data);
if(data.length > 0) {
$.each(data, function (i, floatingip) {
var tr = $("<tr>");
tr.append("<td>" + floatingip.id + "</td>");
tr.append("<td>" + floatingip.floating_ip_address + "</td>");
tr.append("<td>" + floatingip.fixed_ip_address + "</td>");
tr.append("<td>" + floatingip.port_id + "</td>");
tbody.append(tr);
});
var footerMessage = "Displaying " + data.length + " item";
if(data.length > 1)
footerMessage+="s";
$('#floatingips_{{ region }}_{{ project_id }}').find('span').text(footerMessage);
} else {
var resultsDiv = $('#floatingips_{{ region }}_{{ project_id }}').find('#results');
resultsDiv.html("<h4>No Floating IPs in region.</h4>")
}
$('#floatingips_{{ region }}_{{ project_id }}').find('#querying').hide();
$('#floatingips_{{ region }}_{{ project_id }}').find('#results').show();
});
});
</script>
<div id="floatingips_{{ region }}_{{ project_id }}" class="table_wrapper">
<div id="querying">
<h3>Querying floating IPs for region: {{ region }}...</h3>
</div>
<div id="results" style="display: none">
<h3 class="table_title">Floating IPs</h3>
<table class="table table-bordered datatable">
<thead>
<tr>
<th class="normal_column">UUID</th>
<th class="normal_column">Floating Address</th>
<th class="normal_column">Fixed Address</th>
<th class="normal_column">Port ID</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<td colspan="4">
<span class="table_count"></span>
</td>
</tr>
</tfoot>
</table>
</div>
</div>

64
coda/templates/coda/images.html Executable file
View File

@ -0,0 +1,64 @@
<script type="text/javascript">
$(document).ready(function(){
var csrftoken = Cookies.get('csrftoken');
var project_id = "{{ project_id }}";
var region = "{{ region }}";
var update_cache = "{{ update_cache }}";
$.post('/coda/images/', {csrfmiddlewaretoken: csrftoken, project_id: project_id, region: region, update_cache: update_cache}, function(data, status){
var tbody = $('#images_{{ region }}_{{ project_id }}').find('tbody');
data = JSON.parse(data);
if(data.length > 0) {
$.each(data, function (i, image) {
var tr = $("<tr>");
tr.append("<td>" + image.id + "</td>");
tr.append("<td>" + image.name + "</td>");
tr.append("<td>" + image.status + "</td>");
tr.append("<td>" + image.created_at + "</td>");
tbody.append(tr);
});
var footerMessage = "Displaying " + data.length + " item";
if (data.length > 1)
footerMessage += "s";
$('#images_{{ region }}_{{ project_id }}').find('span').text(footerMessage);
} else {
var resultsDiv = $('#images_{{ region }}_{{ project_id }}').find('#results');
resultsDiv.html("<h4>No Images in region.</h4>")
}
$('#images_{{ region }}_{{ project_id }}').find('#querying').hide();
$('#images_{{ region }}_{{ project_id }}').find('#results').show();
});
});
</script>
<div id="images_{{ region }}_{{ project_id }}" class="table_wrapper">
<div id="querying">
<h3>Querying Images for region: {{ region }}...</h3>
</div>
<div id="results" style="display: none">
<h3 class="table_title">Images</h3>
<table class="table table-bordered datatable">
<thead>
<tr>
<th class="normal_column">UUID</th>
<th class="normal_column">Name</th>
<th class="normal_column">Status</th>
<th class="normal_column">Created</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<td colspan="4">
<span class="table_count"></span>
</td>
</tr>
</tfoot>
</table>
</div>
</div>

View File

@ -0,0 +1,4 @@
{% extends "coda/coda/coda_base.html" %}
{% load staticfiles %}

View File

@ -0,0 +1,87 @@
<script type="text/javascript">
$(document).ready(function(){
var csrftoken = Cookies.get('csrftoken');
var project_id = "{{ project_id }}";
var region = "{{ region }}";
var update_cache = "{{ update_cache }}";
$.post('/coda/instances/', {csrfmiddlewaretoken: csrftoken, project_id: project_id, region: region, update_cache: update_cache}, function(data, status) {
var resultsDiv = $('#instances_{{ region }}_{{ project_id }}').find('#results');
data = JSON.parse(data);
if(Object.keys(data).length > 0) {
$.map(data, function (instancesList, userID) {
var tableWrapper = $("<div>").addClass("table_wrapper");
tableWrapper.append("<h3 class=\"table_title\">Instances for User ID: " + userID + "</h3>");
var table = $("<table>");
table.addClass("table")
table.addClass("table-bordered")
table.addClass("datatable")
var thead = $("<thead>");
var tr = $("<tr>");
tr.append($("<th>UUID</th>").addClass("normal_column"));
tr.append($("<th>Name</th>").addClass("normal_column"));
tr.append($("<th>Flavor</th>").addClass("normal_column"));
tr.append($("<th>Status</th>").addClass("normal_column"));
tr.append($("<th>Task</th>").addClass("normal_column"));
tr.append($("<th>Power</th>").addClass("normal_column"));
tr.append($("<th>Hypervisor</th>").addClass("normal_column"));
tr.append($("<th>Image Id</th>").addClass("normal_column"));
tr.append($("<th>Image Name</th>").addClass("normal_column"));
thead.append(tr);
table.append(thead);
var tbody = $("<tbody>");
$.each(instancesList, function (i, instance) {
var tr = $("<tr>");
tr.append("<td>" + instance.id + "</td>");
tr.append("<td>" + instance.name + "</td>");
tr.append("<td>" + instance.flavor.id + "</td>");
tr.append("<td>" + instance.status + "</td>");
tr.append("<td>" + instance["OS-EXT-STS:task_state"] + "</td>");
tr.append("<td>" + instance["OS-EXT-STS:power_state"] + "</td>");
tr.append("<td>" + instance["OS-EXT-SRV-ATTR:hypervisor_hostname"] + "</td>");
tr.append("<td>" + instance.image.id + "</td>");
tr.append("<td>" + instance.image.name + "</td>");
tbody.append(tr);
});
table.append(tbody);
var tfoot = $("<tfoot>");
var tr = $("<tr>");
var td = $("<td colspan='9'>");
var span = $("<span colspan='9'>");
var footerMessage = "Displaying " + instancesList.length + " item";
if (instancesList.length > 1)
footerMessage += "s";
span.append(footerMessage);
td.append(span);
tr.append(td);
tfoot.append(tr);
table.append(tfoot);
tableWrapper.append(table);
resultsDiv.append(tableWrapper);
});
} else {
var resultsDiv = $('#instances_{{ region }}_{{ project_id }}').find('#results');
resultsDiv.html("<h4>No Instances in region.</h4>")
}
$('#instances_{{ region }}_{{ project_id }}').find('#querying').hide();
$('#instances_{{ region }}_{{ project_id }}').find('#results').show();
});
});
</script>
<div id="instances_{{ region }}_{{ project_id }}" class="table_wrapper">
<div id="querying">
<h3>Querying instances for region: {{ region }}...</h3>
</div>
<div id="results" style="display: none">
</div>
</div>

View File

@ -0,0 +1,62 @@
<script type="text/javascript">
$(document).ready(function(){
var csrftoken = Cookies.get('csrftoken');
var project_id = "{{ project_id }}";
var region = "{{ region }}";
var update_cache = "{{ update_cache }}";
$.post('/coda/networks/', {csrfmiddlewaretoken: csrftoken, project_id: project_id, region: region, update_cache: update_cache}, function(data, status){
var tbody = $('#networks_{{ region }}_{{ project_id }}').find('tbody');
data = JSON.parse(data);
if(data.length > 0) {
$.each(data, function (i, network) {
var tr = $("<tr>");
tr.append("<td>" + network.id + "</td>");
tr.append("<td>" + network.name + "</td>");
tr.append("<td>" + network.status + "</td>");
tbody.append(tr);
});
var footerMessage = "Displaying " + data.length + " item";
if (data.length > 1)
footerMessage += "s";
$('#networks_{{ region }}_{{ project_id }}').find('span').text(footerMessage);
} else {
var resultsDiv = $('#networks_{{ region }}_{{ project_id }}').find('#results');
resultsDiv.html("<h4>No Networks in region.</h4>")
}
$('#networks_{{ region }}_{{ project_id }}').find('#querying').hide();
$('#networks_{{ region }}_{{ project_id }}').find('#results').show();
});
});
</script>
<div id="networks_{{ region }}_{{ project_id }}" class="table_wrapper">
<div id="querying">
<h3>Querying networks for region: {{ region }}...</h3>
</div>
<div id="results" style="display: none">
<h3 class="table_title">Networks</h3>
<table class="table table-bordered datatable">
<thead>
<tr>
<th class="normal_column">UUID</th>
<th class="normal_column">Name</th>
<th class="normal_column">Status</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<td colspan="3">
<span class="table_count"></span>
</td>
</tr>
</tfoot>
</table>
</div>
</div>

View File

@ -0,0 +1,11 @@
{% extends "coda/coda/coda_base.html" %}
{% load staticfiles %}
{% block title %}No Project Found{% endblock %}
{% block results %}
<div>
<h2 style="text-align: center">No records found that match Project ID {{ project_id }}.</h2>
</div>
{% endblock %}

View File

@ -0,0 +1,54 @@
{% extends "coda/coda/coda_base.html" %}
{% load staticfiles %}
{% block results %}
<div id="main_content">
<div class="sidebar">
<h2>Project Info</h2>
<form id="coda_form" action="{% url 'horizon:coda:coda:confirm_delete' %}" method="post">
{% csrf_token %}
<p>Project Name: {{ project_info.tenant.name }}</p>
<p>Project ID: {{ project_info.tenant.id}}</p>
<p>Enabled: {{ project_info.tenant.enabled }}</p>
<p>Description: {{ project_info.tenant.description}}</p>
<input type="hidden" name="project_id" value="{{ project_id }}">
{% if users %}
<h4>Users:</h4>
{% for user in users %}
<p>Name: {{ user.name }}</p>
<p>Username: {{ user.username }}</p>
<p>ID: {{ user.id }}</p>
<p>Enabled: {{ user.enabled }}</p>
<p>E-Mail: {{ user.email }}</p>
{% endfor %}
{% else %}
<h4>No Users in this Project</h4>
{% endif %}
<div style="text-align: right; margin-right: 14px; margin-bottom: 10px;">
<a href="{% url 'horizon:coda:coda:index' %}">
<input class="btn" type="button" value="Cancel" />
</a>
<input class="btn btn-primary" type="submit" value="Delete Resources">
</div>
</form>
</div>
<div id='content_body'>
<div class="tenant-header">
<h1>Resources for {{ project_id }}</h1>
</div>
{% for region in regions %}
<h2>Region: {{ region }}</h2>
{% include "coda/coda/instances.html" %}
{% include "coda/coda/floating_ips.html" %}
{% include "coda/coda/security_groups.html" %}
{% include "coda/coda/networks.html" %}
{% include "coda/coda/subnets.html" %}
{% include "coda/coda/routers.html" %}
{% include "coda/coda/volumes.html" %}
{% include "coda/coda/images.html" %}
{% endfor %} <!-- END region for loop -->
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,68 @@
<script type="text/javascript">
$(document).ready(function(){
var csrftoken = Cookies.get('csrftoken');
var project_id = "{{ project_id }}";
var region = "{{ region }}";
var update_cache = "{{ update_cache }}";
$.post('/coda/routers/', {csrfmiddlewaretoken: csrftoken, project_id: project_id, region: region, update_cache: update_cache}, function(data, status){
var tbody = $('#routers_{{ region }}_{{ project_id }}').find('tbody');
data = JSON.parse(data);
if(data.length > 0) {
$.each(data, function (i, router) {
var tr = $("<tr>");
tr.append("<td>" + router.id + "</td>");
tr.append("<td>" + router.name + "</td>");
tr.append("<td>" + router.status + "</td>");
if (router.external_gateway_info != null)
tr.append("<td>" + router.external_gateway_info.network_id + "</td>");
else
tr.append("<td>null</td>");
tbody.append(tr);
});
var footerMessage = "Displaying " + data.length + " item";
if (data.length > 1)
footerMessage += "s";
$('#routers_{{ region }}_{{ project_id }}').find('span').text(footerMessage);
} else {
var resultsDiv = $('#routers_{{ region }}_{{ project_id }}').find('#results');
resultsDiv.html("<h4>No Routers in region.</h4>")
}
$('#routers_{{ region }}_{{ project_id }}').find('#querying').hide();
$('#routers_{{ region }}_{{ project_id }}').find('#results').show();
});
});
</script>
<div id="routers_{{ region }}_{{ project_id }}" class="table_wrapper">
<div id="querying">
<h3>Querying Routers for region: {{ region }}...</h3>
</div>
<div id="results" style="display: none">
<h3 class="table_title">Routers</h3>
<table class="table table-bordered datatable">
<thead>
<tr>
<th class="normal_column">UUID</th>
<th class="normal_column">Name</th>
<th class="normal_column">Status</th>
<th class="normal_column">Gateway</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<td colspan="4">
<span class="table_count"></span>
</td>
</tr>
</tfoot>
</table>
</div>
</div>

View File

@ -0,0 +1,95 @@
<script type="text/javascript">
$(document).ready(function(){
var csrftoken = Cookies.get('csrftoken');
var project_id = "{{ project_id }}";
var region = "{{ region }}";
var update_cache = "{{ update_cache }}";
$.post('/coda/security_groups/', {csrfmiddlewaretoken: csrftoken, project_id: project_id, region: region, update_cache: update_cache}, function(data, status){
var tbody = $('#securitygroups_{{ region }}_{{ project_id }}').find('tbody');
data = JSON.parse(data);
if(data.length > 0) {
$.each(data, function (i, secgroup) {
var tr = $("<tr>");
var rulesBody = $("<tbody>");
tr.append("<td>" + secgroup.name + "</td>");
tr.append("<td>" + secgroup.description + "</td>");
tr.append("<td>" + secgroup.id + "</td>");
$.each(secgroup.security_group_rules, function (i, rule) {
var rulesRow = $("<tr>");
rulesRow.append("<td>" + rule.id + "</td>");
rulesRow.append("<td>" + rule.direction + "</td>");
rulesRow.append("<td>" + rule.protocol + "</td>");
rulesRow.append("<td>" + rule.port_range_min + "</td>");
rulesRow.append("<td>" + rule.port_range_max + "</td>");
rulesRow.append("<td>" + rule.remote_ip_prefix + "</td>");
rulesBody.append(rulesRow);
});
var ruleTable = $("<table>");
var ruleHeaderRow = $("<tr>");
ruleTable.addClass("table")
ruleTable.addClass("table-bordered")
ruleTable.addClass("datatable")
ruleHeaderRow.append($("<th>UUID</th>").addClass("normal_column"));
ruleHeaderRow.append($("<th>Direction</th>").addClass("normal_column"));
ruleHeaderRow.append($("<th>Protocol</th>").addClass("normal_column"));
ruleHeaderRow.append($("<th>Min Port</th>").addClass("normal_column"));
ruleHeaderRow.append($("<th>Max Port</th>").addClass("normal_column"));
ruleHeaderRow.append($("<th>CIDR</th>").addClass("normal_column"));
ruleTable.append($("<thead>").append(ruleHeaderRow));
ruleTable.append(rulesBody);
ruleTable.append($("<tfoot>").append($("<tr>").append($("<td colspan='6'>"))));
tr.append($("<td>").append(ruleTable));
tbody.append(tr);
});
var footerMessage = "Displaying " + data.length + " item";
if (data.length > 1)
footerMessage += "s";
$('#securitygroups_{{ region }}_{{ project_id }}').find('span').text(footerMessage);
} else {
var resultsDiv = $('#securitygroups_{{ region }}_{{ project_id }}').find('#results');
resultsDiv.html("<h4>No Security Groups in region.</h4>")
}
$('#securitygroups_{{ region }}_{{ project_id }}').find('#querying').hide();
$('#securitygroups_{{ region }}_{{ project_id }}').find('#results').show();
});
});
</script>
<div id="securitygroups_{{ region }}_{{ project_id }}" class="table_wrapper">
<div id="querying">
<h3>Querying Security Groups for region: {{ region }}...</h3>
</div>
<div id="results" style="display: none">
<h3 class="table_title">Security Groups</h3>
<table class="table table-bordered datatable">
<thead>
<tr>
<th class="normal_column">Name</th>
<th class="normal_column">Description</th>
<th class="normal_column">UUID</th>
<th class="normal_column">Rules</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<td colspan="4">
<span class="table_count"></span>
</td>
</tr>
</tfoot>
</table>
</div>
</div>

View File

@ -0,0 +1,62 @@
<script type="text/javascript">
$(document).ready(function(){
var csrftoken = Cookies.get('csrftoken');
var project_id = "{{ project_id }}";
var region = "{{ region }}";
var update_cache = "{{ update_cache }}";
$.post('/coda/subnets/', {csrfmiddlewaretoken: csrftoken, project_id: project_id, region: region, update_cache: update_cache}, function(data, status){
var tbody = $('#subnets_{{ region }}_{{ project_id }}').find('tbody');
data = JSON.parse(data);
if(data.length > 0) {
$.each(data, function (i, subnet) {
var tr = $("<tr>");
tr.append("<td>" + subnet.id + "</td>");
tr.append("<td>" + subnet.name + "</td>");
tr.append("<td>" + subnet.cidr + "</td>");
tbody.append(tr);
});
var footerMessage = "Displaying " + data.length + " item";
if (data.length > 1)
footerMessage += "s";
$('#subnets_{{ region }}_{{ project_id }}').find('span').text(footerMessage);
} else {
var resultsDiv = $('#subnets_{{ region }}_{{ project_id }}').find('#results');
resultsDiv.html("<h4>No Subnets in region.</h4>")
}
$('#subnets_{{ region }}_{{ project_id }}').find('#querying').hide();
$('#subnets_{{ region }}_{{ project_id }}').find('#results').show();
});
});
</script>
<div id="subnets_{{ region }}_{{ project_id }}" class="table_wrapper">
<div id="querying">
<h3>Querying Subnets for region: {{ region }}...</h3>
</div>
<div id="results" style="display: none">
<h3 class="table_title">Subnets</h3>
<table class="table table-bordered datatable">
<thead>
<tr>
<th class="normal_column">UUID</th>
<th class="normal_column">Name</th>
<th class="normal_column">CIDR</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<td colspan="3">
<span class="table_count"></span>
</td>
</tr>
</tfoot>
</table>
</div>
</div>

146
coda/templates/coda/volumes.html Executable file
View File

@ -0,0 +1,146 @@
<script type="text/javascript">
$(document).ready(function(){
var csrftoken = Cookies.get('csrftoken');
var project_id = "{{ project_id }}";
var region = "{{ region }}";
var update_cache = "{{ update_cache }}";
$.post('/coda/volumes/', {csrfmiddlewaretoken: csrftoken, project_id: project_id, region: region, update_cache: update_cache}, function(data, status){
var tbody = $('#volumes_{{ region }}_{{ project_id }}').find('tbody');
data = JSON.parse(data);
if(data.length > 0) {
$.each(data, function (i, volume) {
var tr = $("<tr>");
tr.append("<td>" + volume.id + "</td>");
tr.append("<td>" + volume.display_name + "</td>");
tr.append("<td>" + volume.display_description + "</td>");
tr.append("<td>" + volume.created_at + "</td>");
tr.append("<td>" + volume.status + "</td>");
tr.append("<td>" + volume.size + "</td>");
tr.append("<td>" + volume.availability_zone + "</td>");
if (volume.snapshots.length > 0) {
var snapshotsBody = $("<tbody>");
$.each(volume.snapshots, function (i, snapshot) {
var snapshotsRow = $("<tr>");
snapshotsRow.append("<td>" + snapshot.id + "</td>");
snapshotsRow.append("<td>" + snapshot.display_name + "</td>");
snapshotsRow.append("<td>" + snapshot.display_description + "</td>");
snapshotsRow.append("<td>" + snapshot.created_at + "</td>");
snapshotsRow.append("<td>" + snapshot.status + "</td>");
snapshotsRow.append("<td>" + snapshot.size + "</td>");
snapshotsBody.append(snapshotsRow);
});
var snapshotTable = $("<table>");
var snapshotHeaderRow = $("<tr>");
snapshotTable.addClass("table");
snapshotTable.addClass("table-bordered");
snapshotTable.addClass("datatable");
snapshotHeaderRow.append($("<th>UUID</th>").addClass("normal_column"));
snapshotHeaderRow.append($("<th>Name</th>").addClass("normal_column"));
snapshotHeaderRow.append($("<th>Description</th>").addClass("normal_column"));
snapshotHeaderRow.append($("<th>Created</th>").addClass("normal_column"));
snapshotHeaderRow.append($("<th>Status</th>").addClass("normal_column"));
snapshotHeaderRow.append($("<th>Size</th>").addClass("normal_column"));
snapshotTable.append($("<thead>").append(snapshotHeaderRow));
snapshotTable.append(snapshotsBody);
snapshotTable.append($("<tfoot>").append($("<tr>").append($("<td colspan='6'>"))));
tr.append($("<td>").append(snapshotTable));
} else {
tr.append($("<td>").append($("<h4>No Snapshots</h4>")));
}
if (volume.backups.length > 0) {
var backupsBody = $("<tbody>");
$.each(volume.backups, function (i, backup) {
var backupRow = $("<tr>");
backupRow.append("<td>" + backup.id + "</td>");
backupRow.append("<td>" + backup.display_name + "</td>");
backupRow.append("<td>" + backup.display_description + "</td>");
backupRow.append("<td>" + backup.created_at + "</td>");
backupRow.append("<td>" + backup.status + "</td>");
backupRow.append("<td>" + backup.size + "</td>");
backupsBody.append(backupRow);
});
var backupTable = $("<table>");
var backupHeaderRow = $("<tr>");
backupTable.addClass("table");
backupTable.addClass("table-bordered");
backupTable.addClass("datatable");
backupHeaderRow.append($("<th>UUID</th>").addClass("normal_column"));
backupHeaderRow.append($("<th>Name</th>").addClass("normal_column"));
backupHeaderRow.append($("<th>Description</th>").addClass("normal_column"));
backupHeaderRow.append($("<th>Created</th>").addClass("normal_column"));
backupHeaderRow.append($("<th>Status</th>").addClass("normal_column"));
backupHeaderRow.append($("<th>Size</th>").addClass("normal_column"));
backupTable.append($("<thead>").append(backupHeaderRow));
backupTable.append(backupsBody);
backupTable.append($("<tfoot>").append($("<tr>").append($("<td colspan='6'>"))));
tr.append($("<td>").append(backupTable));
} else {
tr.append($("<td>").append($("<h4>No Backups</h4>")));
}
tbody.append(tr);
});
var footerMessage = "Displaying " + data.length + " item";
if (data.length > 1)
footerMessage += "s";
$('#volumes_{{ region }}_{{ project_id }}').find('span').text(footerMessage);
} else {
var resultsDiv = $('#volumes_{{ region }}_{{ project_id }}').find('#results');
resultsDiv.html("<h4>No Volumes in region.</h4>");
}
$('#volumes_{{ region }}_{{ project_id }}').find('#querying').hide();
$('#volumes_{{ region }}_{{ project_id }}').find('#results').show();
});
});
</script>
<div id="volumes_{{ region }}_{{ project_id }}" class="table_wrapper">
<div id="querying">
<h3>Querying Volumes for region: {{ region }}...</h3>
</div>
<div id="results" style="display: none">
<h3 class="table_title">Volumes</h3>
<table class="table table-bordered datatable">
<thead>
<tr>
<th class="normal_column">UUID</th>
<th class="normal_column">Name</th>
<th class="normal_column">Description</th>
<th class="normal_column">Created</th>
<th class="normal_column">Status</th>
<th class="normal_column">Size</th>
<th class="normal_column">Zone</th>
<th class="normal_column">Snapshots</th>
<th class="normal_column">Backups</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<td colspan="9">
<span class="table_count"></span>
</td>
</tr>
</tfoot>
</table>
</div>
</div>

30
coda/tests.py Normal file
View File

@ -0,0 +1,30 @@
# Copyright [2015] Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# 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.
"""
Tests go here.
Not much else to say.
"""
from horizon.test import helpers as test
class CodaTests(test.TestCase):
"""Moar tests."""
def test_me(self):
"""Test me."""
self.assertTrue(1 + 1 == 2)

BIN
coda/tests.pyc Normal file

Binary file not shown.

58
coda/urls.py Normal file
View File

@ -0,0 +1,58 @@
# Copyright [2015] Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# 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.
"""
Url patterns for Coda.
Not much else to say.
"""
from django.conf.urls import patterns
from django.conf.urls import url
from openstack_dashboard.dashboards.coda.coda import views
urlpatterns = patterns(
'',
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^results/$', views.results, name='results'),
url(r'^instances/$', views.instances, name='instances'),
url(r'^floating_ips/$', views.floating_ips, name='floating_ips'),
url(r'^security_groups/$', views.security_groups, name='security_groups'),
url(r'^networks/$', views.networks, name='networks'),
url(r'^subnets/$', views.subnets, name='subnets'),
url(r'^routers/$', views.routers, name='routers'),
url(r'^volumes/$', views.volumes, name='volumes'),
url(r'^images/$', views.images, name='images'),
url(r'^confirm_delete/$', views.confirm_delete, name='confirm_delete'),
url(r'^delete/results/$', views.delete_resources, name='delete_resources'),
url(r'^delete/instances/$',
views.delete_instances,
name='delete_instances'),
url(r'^delete/floating_ips/$',
views.delete_floating_ips,
name='delete_floating_ips'),
url(r'^delete/security_groups/$',
views.delete_security_groups,
name='delete_security_groups'),
url(r'^delete/networks/$', views.delete_networks, name='delete_networks'),
url(r'^delete/routers/$', views.delete_routers, name='delete_routers'),
url(r'^delete/snapshots/$', views.delete_snapshots,
name='delete_snapshots'),
url(r'^delete/backups/$', views.delete_backups, name='delete_backups'),
url(r'^delete/volumes/$', views.delete_volumes, name='delete_volumes'),
url(r'^delete/images/$', views.delete_images, name='delete_images'),
)

BIN
coda/urls.pyc Normal file

Binary file not shown.

574
coda/views.py Normal file
View File

@ -0,0 +1,574 @@
# Copyright [2015] Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# 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.
"""
The Coda views.
Not much else to say right now.
"""
from django.core.cache import cache
from django.http import HttpResponse
from django.shortcuts import render
# from django.conf import settings
from horizon import views
import json
from openstack_dashboard.dashboards.coda.coda import cinder
from openstack_dashboard.dashboards.coda.coda import coda
from openstack_dashboard.dashboards.coda.coda import glance
from openstack_dashboard.dashboards.coda.coda import keystone
from openstack_dashboard.dashboards.coda.coda import neutron
from openstack_dashboard.dashboards.coda.coda import nova
DISPATCH_MAP = {
'instances': nova.list_instances,
'floating_ips': neutron.list_floating_ips,
'security_groups': neutron.list_security_groups,
'networks': neutron.list_networks,
'subnets': neutron.list_subnets,
'routers': neutron.list_routers,
'volumes': cinder.list_volumes,
'snapshots': cinder.list_snapshots,
'backups': cinder.list_backups,
'images': glance.list_images
}
class IndexView(views.APIView):
"""Class based definition of the index view."""
template_name = 'coda/coda/index.html'
def get_data(self, request, context, *args, **kwargs):
"""No context needed."""
return context
def results(request):
"""The results view."""
project_id = request.POST['project_id']
update_cache = request.POST.get('update_cache', 'False')
auth_token = coda.get_auth_token()
if auth_token == 'error':
msg = "Coda User failed to authenticate with the Identity Service."
context = {
'project_id': project_id,
'error_message': msg
}
return render(request, 'coda/coda/error.html', context)
project_exists, project_info = keystone.project_exists(
auth_token,
project_id)
if project_exists:
users = keystone.get_project_users(auth_token, project_id)
regions = coda.get_coda_regions()
context = {
'project_id': project_id,
'project_info': project_info,
'users': users,
'regions': regions,
'update_cache': update_cache,
}
return render(request, 'coda/coda/results.html', context)
else:
context = {
'project_id': project_id
}
return render(request, 'coda/coda/no_project.html', context)
def instances(request):
"""The instances view."""
project_id = request.POST['project_id']
region = request.POST['region']
update_cache = request.POST.get('update_cache', 'True')
instances_dict = get_project_resource(
region,
project_id,
'instances',
update_cache)
# todo (nathan) fix this later broken during port from hp pub cloud
# images_key = region + '_coda_images'
# coda_images = cache.get(images_key)
coda_images = None
if coda_images is not None:
image_list = []
if 'public' in coda_images:
image_list.extend(coda_images['public'])
if project_id in coda_images:
image_list.extend(coda_images[project_id])
coda.fill_image_info(instances_dict, image_list)
else:
coda.fill_image_info(instances_dict, None)
return HttpResponse(json.dumps(instances_dict))
def floating_ips(request):
"""The floating ips view."""
project_id = request.POST['project_id']
region = request.POST['region']
update_cache = request.POST['update_cache']
floating_ips_dict = get_project_resource(
region,
project_id,
'floating_ips',
update_cache)
return HttpResponse(json.dumps(floating_ips_dict))
def security_groups(request):
"""The security groups view."""
project_id = request.POST['project_id']
region = request.POST['region']
update_cache = request.POST['update_cache']
security_groups_dict = get_project_resource(
region,
project_id,
'security_groups',
update_cache)
return HttpResponse(json.dumps(security_groups_dict))
def networks(request):
"""The networks view."""
project_id = request.POST['project_id']
region = request.POST['region']
update_cache = request.POST['update_cache']
networks_dict = get_project_resource(
region,
project_id,
'networks',
update_cache)
return HttpResponse(json.dumps(networks_dict))
def subnets(request):
"""The subnets view."""
project_id = request.POST['project_id']
region = request.POST['region']
update_cache = request.POST['update_cache']
subnets_dict = get_project_resource(
region,
project_id,
'subnets',
update_cache)
return HttpResponse(json.dumps(subnets_dict))
def routers(request):
"""The routers view."""
project_id = request.POST['project_id']
region = request.POST['region']
update_cache = request.POST['update_cache']
routers_dict = get_project_resource(
region,
project_id,
'routers',
update_cache)
return HttpResponse(json.dumps(routers_dict))
def volumes(request):
"""The volumes view."""
project_id = request.POST['project_id']
region = request.POST['region']
update_cache = request.POST['update_cache']
volumes_dict = get_project_resource(
region,
project_id,
'volumes',
update_cache)
snapshots_dict = get_project_resource(
region,
project_id,
'snapshots',
update_cache)
backups_dict = get_project_resource(
region,
project_id,
'backups',
update_cache)
volumes_dict = coda.fill_volume_info(
volumes_dict,
snapshots_dict,
backups_dict)
return HttpResponse(json.dumps(volumes_dict))
def images(request):
"""The images view."""
project_id = request.POST['project_id']
region = request.POST['region']
update_cache = request.POST['update_cache']
images_dict = None
images_key = region + '_' + project_id + '_images'
project_images = cache.get(images_key)
if update_cache == 'True':
images_dict = get_all_images(region, True)
elif project_images is None:
images_dict = get_all_images(region, False)
if images_dict is not None:
if project_id in images_dict:
project_images = images_dict[project_id]
else:
project_images = {}
cache.set(images_key, project_images, 600)
return HttpResponse(json.dumps(project_images))
def get_all_images(region, reload_cache=False):
"""Utility method, might belong elsewhere."""
images_key = region + '_coda_images'
coda_images = cache.get(images_key)
if reload_cache is True or coda_images is None or len(coda_images) == 0:
auth_token = coda.get_auth_token()
images_dict = glance.list_all_images(auth_token, region)
coda_images = images_dict
cache.set(images_key, coda_images, 3600)
return coda_images
def confirm_delete(request):
"""The confirm delete view."""
project_id = request.POST['project_id']
auth_token = coda.get_auth_token()
project_exists, project_info = keystone.project_exists(
auth_token,
project_id)
users = keystone.get_project_users(auth_token, project_id)
regions = coda.get_coda_regions()
context = {
'project_id': project_id,
'project_info': project_info,
'users': users,
'regions': regions,
}
return render(request, 'coda/coda/confirm_delete.html', context)
def delete_resources(request):
"""The deleting resources view."""
project_id = request.POST['project_id']
os_tenant_id = request.POST['os_tenant_id']
os_username = request.POST['os_username']
os_password = request.POST['os_password']
user_token = keystone.user_authenticate(
os_tenant_id,
os_username,
os_password)
project_exists, project_info = keystone.project_exists(
user_token,
project_id)
users = keystone.get_project_users(user_token, project_id)
regions = coda.get_coda_regions()
context = {
'project_id': project_id,
'project_info': project_info,
'users': users,
'regions': regions,
'user_token': user_token
}
return render(request, 'coda/coda/delete/results.html', context)
def delete_instances(request):
"""The deleting instances view."""
result = {}
project_id = request.POST['project_id']
user_token = request.POST['user_token']
region = request.POST['region']
instance_dict = remove_project_resource(region, project_id, 'instances')
if instance_dict is not None:
for user_id, instance_list in instance_dict.iteritems():
for instance in instance_list:
result[instance['id']] = nova.delete_instance(
user_token,
region,
project_id,
instance['id'])
else:
result = 'ERROR'
return HttpResponse(json.dumps(result))
def delete_floating_ips(request):
"""The deleting floating ips view."""
result = {}
project_id = request.POST['project_id']
user_token = request.POST['user_token']
region = request.POST['region']
floating_ips_dict = remove_project_resource(
region,
project_id,
'floating_ips')
if floating_ips_dict is not None:
for floating_ip in floating_ips_dict:
result[floating_ip['id']] = neutron.delete_floating_ip(
user_token,
region,
project_id,
floating_ip['id'])
else:
result = 'ERROR'
return HttpResponse(json.dumps(result))
def delete_security_groups(request):
"""The deleting security groups view."""
result = {}
project_id = request.POST['project_id']
user_token = request.POST['user_token']
region = request.POST['region']
security_groups_dict = remove_project_resource(
region,
project_id,
'security_groups')
if security_groups_dict is not None:
for sec_group in security_groups_dict:
result[sec_group['id']] = neutron.delete_security_group(
user_token,
region,
project_id,
sec_group['id'])
else:
result = 'ERROR'
return HttpResponse(json.dumps(result))
def delete_networks(request):
"""The deleting networks view."""
result = {}
project_id = request.POST['project_id']
user_token = request.POST['user_token']
region = request.POST['region']
networks_dict = remove_project_resource(region, project_id, 'networks')
remove_project_resource(region, project_id, 'subnets')
if networks_dict is not None:
for network in networks_dict:
result[network['id']] = neutron.delete_network(
user_token,
region,
project_id,
network['id'])
else:
result = 'ERROR'
return HttpResponse(json.dumps(result))
def delete_routers(request):
"""The deleting routers view."""
result = {}
project_id = request.POST['project_id']
user_token = request.POST['user_token']
region = request.POST['region']
routers_dict = remove_project_resource(region, project_id, 'routers')
if routers_dict is not None:
for router in routers_dict:
result[router['id']] = neutron.delete_router(
user_token,
region,
project_id,
router['id'])
else:
result = 'ERROR'
return HttpResponse(json.dumps(result))
def delete_volumes(request):
"""The deleting volumes view."""
result = {}
project_id = request.POST['project_id']
user_token = request.POST['user_token']
region = request.POST['region']
volumes_dict = remove_project_resource(region, project_id, 'volumes')
if volumes_dict is not None:
for volume in volumes_dict:
result[volume['id']] = cinder.delete_volume(
user_token,
region,
project_id,
volume['id'])
else:
result = 'ERROR'
return HttpResponse(json.dumps(result))
def delete_snapshots(request):
"""The deleting snapshots view."""
result = {}
project_id = request.POST['project_id']
user_token = request.POST['user_token']
region = request.POST['region']
snapshots_dict = remove_project_resource(region, project_id, 'snapshots')
if snapshots_dict is not None:
for snapshot in snapshots_dict:
result[snapshot['id']] = cinder.delete_snapshot(
user_token,
region,
project_id,
snapshot['id'])
return HttpResponse(json.dumps(result))
def delete_backups(request):
"""The deleting backups view."""
result = {}
project_id = request.POST['project_id']
user_token = request.POST['user_token']
region = request.POST['region']
backups_dict = remove_project_resource(region, project_id, 'backups')
if backups_dict is not None:
for backup in backups_dict:
result[backup['id']] = cinder.delete_backup(
user_token,
region,
project_id,
backup['id'])
return HttpResponse(json.dumps(result))
def delete_images(request):
"""The deleting images view."""
result = {}
project_id = request.POST['project_id']
user_token = request.POST['user_token']
region = request.POST['region']
images_key = region + '_' + project_id + '_images'
tenant_images = cache.get(images_key)
error_images = []
if tenant_images is not None:
# todo (nathan) improve this
# remove the tenant image cached
# cache.delete(images_key)
# scrubber_images = get_all_images(region)
# scrubber_images.pop(tenant_id, None)
# cache.set('scrubber_images', scrubber_images, 3600)
for image in tenant_images:
result[image['id']] = glance.delete_image(
user_token,
region,
image['id'])
if result[image['id']] != 'Image Deleted':
error_images.append(image)
else:
# todo (nathan) log that there were no images to delete
result = {}
# related to above, this is hackish, but it works for now
if len(error_images) > 0:
cache.set(images_key, error_images, 600)
else:
cache.set(images_key, {}, 600)
return HttpResponse(json.dumps(result))
def get_project_resource(region, project_id, resource, update_cache='True'):
"""Utility method to get resource map from cache."""
resource_map = 'ERROR'
resource_key = region + '_' + project_id + '_' + resource
if resource in DISPATCH_MAP:
if update_cache == 'True':
coda_token = coda.get_auth_token()
resource_map = DISPATCH_MAP[resource](
coda_token,
region,
project_id)
else:
resource_map = cache.get(resource_key)
if resource_map is None:
coda_token = coda.get_auth_token()
resource_map = DISPATCH_MAP[resource](
coda_token,
region,
project_id)
cache.set(resource_key, resource_map, 600)
return resource_map
def remove_project_resource(region, tenant_id, resource):
"""Utility method to remove resource map from cache."""
resource_key = region + '_' + tenant_id + '_' + resource
resource_map = cache.get(resource_key)
if resource_map is not None:
cache.delete(resource_key)
return resource_map

BIN
coda/views.pyc Normal file

Binary file not shown.

290
create-resources.sh Executable file
View File

@ -0,0 +1,290 @@
#!/bin/bash
# TODO add how to use this
source set_coda_env.sh
function hasNetwork {
networks=`neutron net-list | awk '{if ($2 != "id" && $2 != "|" && NF>1) print $2 " " $4}' | grep -v "ext-net"`
if [ ${#networks} -eq 0 ]
then
CODA_NETWORK_ID=""
return -1
else
parts=($networks)
CODA_NETWORK_ID=${parts[0]}
fi
}
function hasSecGroups {
expectedCount=$1
groups=`neutron security-group-list | awk '{if ($2 != "id" && NF>1) print $4}' | grep -v default`
if [ ${#groups} -eq 0 ]
then
echo "No Security Groups"
return -1
else
parts=($groups)
if [ ${#parts[@]} -ne $expectedCount ]
then
echo "WARNING: Unexpected Number of Security Groups."
return -1
fi
fi
return 0
}
function createSecurityGroup {
groupName=$1
description=$2
exists=`neutron security-group-show $groupName`
if [[ ${exists:0:1} == "+" ]]
then
echo "Group $groupName already exists."
return -1
else
neutron security-group-create $groupName --description "$description"
return 0
fi
}
function createFloatingIPs {
count=$1
floatingIPs=`neutron floatingip-list | awk '{if ($2 != "id" && NF>1) print $2}'`
if [ ${#floatingIPs} -eq 0 ]
then
echo "Creating $count floating IP's."
for x in $(seq 1 $count)
do
neutron floatingip-create Ext-Net
done
else
parts=($floatingIPs)
if [ ${#parts[@]} -gt $count ]
then
echo "WARNING: Too many floating IP's."
elif [ ${#parts[@]} -lt $count ]
then
diff=`expr $count - ${#parts[@]}`
echo "Not enough floating IP's, creating $diff more."
for x in $(seq 1 $diff)
do
neutron floatingip-create Ext-Net
done
else
echo "Floating IP's already created."
fi
fi
return 0
}
function assignAllFloatingIPs {
for x in `nova list | awk '{if ($2 != "ID" && NF>1 && $13 == "|") print $2}'`;
do port=`neutron port-list -- --device_id $x | awk '{if ($2 != "id" && NF>1) print $2}'`;
fip=`neutron floatingip-list | grep '| *|' | head -1 | awk '{print $2}'`;
neutron floatingip-associate $fip $port;
done;
}
function hasInstances {
expectedCount=$1
instances=`nova list | awk '{if ($2 != "ID" && NF>1) print $4}'`
if [ ${#instances} -eq 0 ]
then
echo "No Instances"
return -1
else
parts=($instances)
if [ ${#parts[@]} -ne $expectedCount ]
then
echo "WARNING: Unexpected Number of instances."
fi
fi
return 0
}
function createVolumes {
count=$1
name=$2
volumes=`cinder list | awk '{if ($2 != "ID" && NF>1) print $2}'`
if [ ${#volumes} -eq 0 ]
then
echo "Creating $count Volumes."
for x in $(seq 1 $count)
do
cinder create --display-name "$name-0$x" --display-description "Testing a volume $x" 5
done
else
parts=($volumes)
if [ ${#parts[@]} -gt $count ]
then
echo "WARNING: Too many Volumes."
elif [ ${#parts[@]} -lt $count ]
then
diff=`expr $count - ${#parts[@]}`
echo "Not enough Volumes, creating $diff more."
for x in $(seq 1 $diff)
do
cinder create --display-name "$name-0$x" --display-description "Testing a volume $x" 5
done
else
echo "Volumes already created."
fi
fi
}
function createSnapshots {
volumes=`cinder list | awk '{if ($2 != "ID" && NF>1) print $2}'`
snapshots=`cinder snapshot-list | awk '{if ($2 != "ID" && NF>1) print $4}'`
for vol in $volumes
do
exists=`echo $snapshots | grep $vol`
echo $exists
if [ ${#exists} -eq 0 ]
then
echo "Creating Snapshot of $vol."
cinder snapshot-create --display-name "snapshot_$vol" --display-description 'Testing a snapshot' $vol
else
echo "Snapshot for Volume: $vol already exists."
fi
done
}
function createBackups {
volumes=`cinder list | awk '{if ($2 != "ID" && NF>1) print $2}'`
backups=`cinder backup-list | awk '{if ($2 != "ID" && NF>1) print $4}'`
for vol in $volumes
do
exists=`echo $backups | grep $vol`
echo $exists
if [ ${#exists} -eq 0 ]
then
echo "Creating Backup of $vol."
cinder backup-create --display-name "backup_$vol" --display-description 'Testing a backup.' $vol
else
echo "Backup for Volume: $vol already exists."
fi
done
}
function createImages {
instances=`nova list | awk '{if ($2 != "ID" && NF>1) print $4}'`
images=`glance -k image-list --property-filter owner_id=$OS_TENANT_ID | awk '{if ($2 != "ID" && NF>1) print $4}'`
for i in $instances
do
exists=`echo $images | grep "$i-image"`
echo $exists
if [ ${#exists} -eq 0 ]
then
echo "Creating Image of $i."
nova image-create $i "$i-image"
else
echo "Image of Instance: $i already exists."
fi
done
}
#==============================================================================================================================================================
# Create Resources for Project 1
echo "Creating Resources for Project 1"
export OS_USERNAME=$CODA_USER_1
export OS_TENANT_NAME=$CODA_PROJECT_NAME_1
export OS_TENANT_ID=$CODA_PROJECT_ID_1
# Network for Project 1
hasNetwork
if [ ${#CODA_NETWORK_ID} -eq 0 ]
then
echo "creating coda-network"
neutron net-create coda-network
neutron subnet-create coda-network 10.0.0.0/24 --name coda-subnet
neutron net-create coda2-net
neutron subnet-create coda2-net 10.10.0.0/16 --name coda2-subnet
neutron router-create coda-router
neutron router-interface-add coda-router coda-subnet
neutron router-interface-add coda-router coda2-subnet
neutron router-gateway-set coda-router Ext-Net
hasNetwork #Make sure we get the variable set when done.
else
echo "coda-network already created."
fi
# Security Groups for Project 1
echo "Creating Security Groups."
if createSecurityGroup basenode 'The base security group for all nodes.'
then
neutron security-group-rule-create --direction ingress --protocol tcp --port_range_min 22 --port_range_max 22 --remote-ip-prefix 10.0.0.0/8 basenode
neutron security-group-rule-create --direction ingress --protocol tcp --port_range_min 9182 --port_range_max 9182 --remote-ip-prefix 10.0.0.0/8 basenode
fi
if createSecurityGroup chef-server 'Opens Chef ports'
then
neutron security-group-rule-create --direction ingress --protocol tcp --port_range_min 4000 --port_range_max 4000 --remote-ip-prefix 10.0.0.0/8 chef-server
fi
if createSecurityGroup web-server 'Good for a web server'
then
neutron security-group-rule-create --direction ingress --protocol tcp --port_range_min 80 --port_range_max 80 --remote-ip-prefix 10.0.0.0/8 web-server
neutron security-group-rule-create --direction ingress --protocol tcp --port_range_min 81 --port_range_max 81 --remote-ip-prefix 10.0.0.0/8 web-server
neutron security-group-rule-create --direction ingress --protocol tcp --port_range_min 443 --port_range_max 443 --remote-ip-prefix 10.0.0.0/8 web-server
neutron security-group-rule-create --direction ingress --protocol tcp --port_range_min 8080 --port_range_max 8080 --remote-ip-prefix 10.0.0.0/8 web-server
neutron security-group-rule-create --direction ingress --protocol tcp --port_range_min 8081 --port_range_max 8081 --remote-ip-prefix 10.0.0.0/8 web-server
fi
if createSecurityGroup email 'email ports'
then
neutron security-group-rule-create --direction ingress --protocol tcp --port_range_min 25 --port_range_max 25 --remote-ip-prefix 10.0.0.0/8 email
neutron security-group-rule-create --direction ingress --protocol tcp --port_range_min 110 --port_range_max 110 --remote-ip-prefix 10.0.0.0/8 email
neutron security-group-rule-create --direction ingress --protocol tcp --port_range_min 143 --port_range_max 143 --remote-ip-prefix 10.0.0.0/8 email
neutron security-group-rule-create --direction ingress --protocol tcp --port_range_min 465 --port_range_max 465 --remote-ip-prefix 10.0.0.0/8 email
neutron security-group-rule-create --direction ingress --protocol tcp --port_range_min 993 --port_range_max 993 --remote-ip-prefix 10.0.0.0/8 email
neutron security-group-rule-create --direction ingress --protocol tcp --port_range_min 995 --port_range_max 995 --remote-ip-prefix 10.0.0.0/8 email
neutron security-group-rule-create --direction ingress --protocol tcp --port_range_min 2525 --port_range_max 2525 --remote-ip-prefix 10.0.0.0/8 email
fi
# Instances for Project 1
if hasInstances 4
then
echo "Instances already created."
else
echo "Creating instances."
# Two for user trumpetsherald
nova boot --flavor $CODA_FLAVOR --image $CODA_IMAGE_1 --key_name $CODA_KEY --availability-zone $CODA_AZ1 --security-groups basenode,web-server --nic net-id=$CODA_NETWORK_ID blogs;
nova boot --flavor $CODA_FLAVOR --image $CODA_IMAGE_2 --key_name $CODA_KEY --availability-zone $CODA_AZ2 --security-groups basenode,chef-server --nic net-id=$CODA_NETWORK_ID chef-server;
# Two for user nkimball
export OS_USERNAME=$CODA_USER_2
nova boot --flavor $CODA_FLAVOR --image $CODA_IMAGE_3 --key_name $CODA_KEY --availability-zone $CODA_AZ1 --security-groups basenode,email --nic net-id=$CODA_NETWORK_ID email;
nova boot --flavor $CODA_FLAVOR --image $CODA_IMAGE_4 --key_name $CODA_KEY --availability-zone $CODA_AZ2 --security-groups basenode,web-server --nic net-id=$CODA_NETWORK_ID web-server;
fi
# Floating IP's for Project 1
createFloatingIPs 4
sleep 10
# Assign All the Floating IP's
assignAllFloatingIPs
# Create Volumes for Project 1
createVolumes 2 'test'
echo "I'm sleeping for 30 seconds to allow the instances to build before creating images of them."
sleep 30
#createSnapshots
#createBackups
createImages
#==============================================================================================================================================================

45
dashboard.py Normal file
View File

@ -0,0 +1,45 @@
# Copyright [2015] Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# 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.
"""The coda dashboard module.
Coda is a Horizon dashboard and panel (both share the name) that
facilitates resource clean up of a project once that project is no longer
needed http://openstack.org
"""
from django.utils.translation import ugettext_lazy as _
import horizon
class Codagroup(horizon.PanelGroup):
"""Defines the coda group, currently not used."""
slug = "codagroup"
name = _("Coda Group")
panels = ('coda',)
class Coda(horizon.Dashboard):
"""The coda panel."""
name = _("Coda")
slug = "coda"
panels = ('coda',)
default_panel = 'coda'
horizon.register(Coda)

BIN
dashboard.pyc Normal file

Binary file not shown.

19
models.py Normal file
View File

@ -0,0 +1,19 @@
# Copyright [2015] Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# 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.
"""
This is a stub file.
Stub file to work around django bug: https://code.djangoproject.com/ticket/7198
"""

BIN
models.pyc Normal file

Binary file not shown.

29
set_coda_env.sh Executable file
View File

@ -0,0 +1,29 @@
#!/bin/bash
export CODA_USER_1=coda_user
export CODA_USER_2=coda_user2
export OS_PASSWORD=hpinvent
export CODA_PROJECT_NAME_1="coda_project"
export CODA_PROJECT_ID_1=8a8b30b83aab4b0d9044b883ab95cd46
#export CODA_TENANT_NAME_2="Kimballs-Project"
#export CODA_TENANT_ID_2=10100821657591
export OS_AUTH_URL=http://10.23.201.133:5000/v2.0/
#export OS_REGION_NAME=region-a.geo-1
export NOVACLIENT_INSECURE=True
export NEUTRONCLIENT_INSECURE=True
export CINDERCLIENT_INSECURE=True
export CODA_KEY=hpcloud
export CODA_IMAGE_1=983ceae5-046e-46b1-a6ca-22fce45c3d15
export CODA_IMAGE_2=983ceae5-046e-46b1-a6ca-22fce45c3d15
export CODA_IMAGE_3=983ceae5-046e-46b1-a6ca-22fce45c3d15
export CODA_IMAGE_4=983ceae5-046e-46b1-a6ca-22fce45c3d15
export CODA_FLAVOR=2
export CODA_AZ1=nova
export CODA_AZ2=nova

27
set_coda_env.sh.example Normal file
View File

@ -0,0 +1,27 @@
#!/bin/bash
export CODA_USER_1=coda_user
export CODA_USER_2=coda_user2
export OS_PASSWORD=coda_p@ssw0rd
export CODA_PROJECT_NAME_1="coda_project"
export CODA_PROJECT_ID_1=project_guid
export OS_AUTH_URL=http://127.0.0.1:5000/v2.0/
#export OS_REGION_NAME=region-a.geo-1
export NOVACLIENT_INSECURE=True
export NEUTRONCLIENT_INSECURE=True
export CINDERCLIENT_INSECURE=True
export CODA_KEY=hpcloud
export CODA_IMAGE_1=imgae_guid
export CODA_IMAGE_2=imgae_guid
export CODA_IMAGE_3=imgae_guid
export CODA_IMAGE_4=imgae_guid
export CODA_FLAVOR=2
export CODA_AZ1=nova
export CODA_AZ2=nova

1
static/coda/js/coda.js Normal file
View File

@ -0,0 +1 @@
/* Additional JavaScript for coda. */

137
static/coda/js/js.cookie.js Executable file
View File

@ -0,0 +1,137 @@
/*!
* JavaScript Cookie v2.0.0-pre
* https://github.com/js-cookie/js-cookie
*
* Copyright 2006, 2015 Klaus Hartl
* Released under the MIT license
*/
(function (factory) {
if (typeof define === 'function' && define.amd) {
define(factory);
} else if (typeof exports === 'object') {
module.exports = factory();
} else {
var _OldCookies = window.Cookies;
var api = window.Cookies = factory(window.jQuery);
api.noConflict = function () {
window.Cookies = _OldCookies;
return api;
};
}
}(function () {
function extend () {
var i = 0;
var result = {};
for (; i < arguments.length; i++) {
var attributes = arguments[ i ];
for (var key in attributes) {
result[key] = attributes[key];
}
}
return result;
}
function init (converter) {
function api (key, value, attributes) {
var result;
// Write
if (arguments.length > 1) {
attributes = extend({
path: '/'
}, api.defaults, attributes);
if (typeof attributes.expires === 'number') {
var expires = new Date();
expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5);
attributes.expires = expires;
}
try {
result = JSON.stringify(value);
if (/^[\{\[]/.test(result)) {
value = result;
}
} catch (e) {}
value = encodeURIComponent(String(value));
value = value.replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent);
key = encodeURIComponent(String(key));
key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent);
key = key.replace(/[\(\)]/g, escape);
return (document.cookie = [
key, '=', value,
attributes.expires && '; expires=' + attributes.expires.toUTCString(), // use expires attribute, max-age is not supported by IE
attributes.path && '; path=' + attributes.path,
attributes.domain && '; domain=' + attributes.domain,
attributes.secure && '; secure'
].join(''));
}
// Read
if (!key) {
result = {};
}
// To prevent the for loop in the first place assign an empty array
// in case there are no cookies at all. Also prevents odd result when
// calling "get()"
var cookies = document.cookie ? document.cookie.split('; ') : [];
var rdecode = /(%[0-9A-Z]{2})+/g;
var i = 0;
for (; i < cookies.length; i++) {
var parts = cookies[i].split('=');
var name = parts[0].replace(rdecode, decodeURIComponent);
var cookie = parts.slice(1).join('=');
if (cookie.charAt(0) === '"') {
cookie = cookie.slice(1, -1);
}
cookie = converter && converter(cookie, name) || cookie.replace(rdecode, decodeURIComponent);
if (this.json) {
try {
cookie = JSON.parse(cookie);
} catch (e) {}
}
if (key === name) {
result = cookie;
break;
}
if (!key) {
result[name] = cookie;
}
}
return result;
}
api.get = api.set = api;
api.getJSON = function () {
return api.apply({
json: true
}, [].slice.call(arguments));
};
api.defaults = {};
api.remove = function (key, attributes) {
api(key, '', extend(attributes, {
expires: -1
}));
};
api.withConverter = init;
return api;
}
return init();
}));

View File

@ -0,0 +1 @@
/* Additional SCSS for {{ dash_name }}. */

11
templates/coda/base.html Normal file
View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% block sidebar %}
{% include 'horizon/common/_sidebar.html' %}
{% endblock %}
{% block main %}
{% include "horizon/_messages.html" %}
{% block coda_main %}{% endblock %}
{% endblock %}

5
templatetags/__init__.py Normal file
View File

@ -0,0 +1,5 @@
"""Utilities for use in templates.
The jsfunc method replaces - with _ to allow for proper java script
function names.
"""

BIN
templatetags/__init__.pyc Normal file

Binary file not shown.

View File

@ -0,0 +1,38 @@
# Copyright [2015] Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# 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.
"""Utilities for use in templates.
The jsfunc method replaces - with _ to allow for proper java script
function names.
"""
from django import template
from django.template.defaultfilters import stringfilter
register = template.Library()
@register.filter
def get(mapping, key):
"""Safe way to get a value from a dict."""
return mapping.get(key, '')
@register.filter
@stringfilter
def jsfunc(value):
"""Create a safe javascript function name."""
return value.replace('-', '_')

Binary file not shown.