Initial commit

This commit is contained in:
Florent Flament 2014-02-14 13:34:25 +01:00
commit 98008cb337
17 changed files with 1790 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
.idea/
*~
*.pyc
AUTHORS
ChangeLog
build/
dist/
ospurge.egg-info/
pbr-0.6-py2.7.egg/

24
README.md Normal file
View File

@ -0,0 +1,24 @@
License / Copyright
-------------------
This software is released under the MIT License.
Copyright (c) 2014 Cloudwatt
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

5
debian/changelog vendored Normal file
View File

@ -0,0 +1,5 @@
ospurge (0.1.0-1) unstable; urgency=low
* source package automatically created by stdeb 0.6.0
-- nacim <nassim.babaci@cloudwatt.com> Mon, 10 Feb 2014 17:52:47 +0100

1
debian/compat vendored Normal file
View File

@ -0,0 +1 @@
7

17
debian/control vendored Normal file
View File

@ -0,0 +1,17 @@
Source: ospurge
Maintainer: unknown <unknown@unknown>
Section: python
Priority: optional
Build-Depends: python-setuptools (>= 0.6b3), debhelper (>= 7), python-support (>= 0.8.4)
Standards-Version: 3.8.4
XS-Python-Version: current
Package: python-ospurge
Architecture: all
Depends: ${misc:Depends}, ${python:Depends}, python-ceilometerclient, python-cinderclient, python-glanceclient, python-keystoneclient, python-neutronclient, python-novaclient, python-swiftclient
XB-Python-Version: ${python:Versions}
Provides: ${python:Provides}
Description: UNKNOWN
.
.
.

14
debian/python-resourcesmanager.preinst vendored Normal file
View File

@ -0,0 +1,14 @@
#! /bin/sh
set -e
# This was added by stdeb to workaround Debian #479852. In a nutshell,
# pycentral does not remove normally remove its symlinks on an
# upgrade. Since we're using python-support, however, those symlinks
# will be broken. This tells python-central to clean up any symlinks.
if [ -e /var/lib/dpkg/info/python-ospurge.list ] && which pycentral >/dev/null 2>&1
then
pycentral pkgremove python-ospurge
fi
#DEBHELPER#

22
debian/rules vendored Executable file
View File

@ -0,0 +1,22 @@
#!/usr/bin/make -f
# This file was automatically generated by stdeb 0.6.0 at
# Mon, 10 Feb 2014 17:52:47 +0100
# Unset the environment variables set by dpkg-buildpackage. (This is
# necessary because distutils is brittle with compiler/linker flags
# set. Specifically, packages using f2py will break without this.)
unexport CPPFLAGS
unexport CFLAGS
unexport CXXFLAGS
unexport FFLAGS
unexport LDFLAGS
#exports specified using stdeb Setup-Env-Vars:
export DH_OPTIONS=--buildsystem=python_distutils
%:
dh $@

1
debian/source/format vendored Normal file
View File

@ -0,0 +1 @@
1.0

1
ospurge/__init__.py Normal file
View File

@ -0,0 +1 @@

588
ospurge/os_purge.py Executable file
View File

@ -0,0 +1,588 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
#
# This software is released under the MIT License.
#
# Copyright (c) 2014 Cloudwatt
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import argparse
import itertools
import logging
import os
from requests.exceptions import ConnectionError
import sys
import time
from ceilometerclient.v2 import client as ceilometer_client
from cinderclient.v1 import client as cinder_client
from glanceclient.v1 import client as glance_client
from keystoneclient.apiclient import exceptions as api_exceptions
from keystoneclient.v2_0 import client as keystone_client
from neutronclient.v2_0 import client as neutron_client
from novaclient.v1_1 import client as nova_client
from swiftclient import client as swift_client
RETRIES = 3
TIMEOUT = 5 # 5 seconds timeout between retries
class EndpointNotFound(Exception):
pass
class NoSuchProject(Exception):
ERROR_CODE = 2
AUTHENTICATION_FAILED_ERROR_CODE = 3
class DeletionFailed(Exception):
ERROR_CODE = 4
CONNECTION_ERROR_CODE = 5
class ConnectionFailed(Exception):
ERROR_CODE = 6
# Available resources classes
RESOURCES_CLASSES = ['CinderSnapshots',
'NovaServers',
'NeutronFloatingIps',
'NeutronInterfaces',
'NeutronRouters',
'NeutronNetworks',
'NeutronSecgroups',
'GlanceImages',
'SwiftObjects',
'SwiftContainers',
'CinderVolumes',
'CeilometerAlarms']
### Decorators
def retry(service_name):
def factory(func):
"""Decorator allowing to retry in case of failure"""
def wrapper(*args, **kwargs):
n = 0
while True :
try:
return func(*args, **kwargs)
except Exception as e:
if n == RETRIES:
raise DeletionFailed(service_name)
n += 1
logging.info("* Deletion failed - "
"Retrying in {} seconds - "
"Retry count {}".format(TIMEOUT, n))
time.sleep(TIMEOUT)
return wrapper
return factory
### Classes
class Session(object):
"""
A Session stores information that can be user by the different
Openstack Clients. The most important data is:
* self.token - The Openstack token to be used accross services;
* self.catalog - Allowing to retrieve services' endpoints.
"""
def __init__(self, username, password, project_name,
auth_url, endpoint_type="publicURL"):
client = keystone_client.Client(
username=username, password=password,
tenant_name=project_name, auth_url=auth_url)
# Storing username, password, project_name and auth_url for
# use by clients librarties that cannot use an existing token.
self.username = username
self.password = password
self.project_name = project_name
self.auth_url = auth_url
# Session variables to be used by clients when possible
self.token = client.auth_token
self.user_id = client.user_id
self.project_id = client.project_id
self.endpoint_type = endpoint_type
self.catalog = client.service_catalog.get_endpoints()
def get_endpoint(self, service_type):
try:
return self.catalog[service_type][0][self.endpoint_type]
except KeyError:
# Endpoint could not be found
raise EndpointNotFound(service_type)
class Resources(object):
"""
Abstract base class for all resources to be removed.
"""
def __init__(self, session):
self.session = session
def list(self):
pass
def delete(self, resource):
"""
Displays informational message about a resource deletion.
"""
logging.info("* Deleting {}.".format(self.resource_str(resource)))
def purge(self):
"Delete all resources."
c_name = self.__class__.__name__
logging.info("* Purging {}".format(c_name))
for resource in self.list():
retry(c_name)(self.delete)(resource)
def dump(self):
"Display all available resources."
c_name = self.__class__.__name__
print "Resources type: {}".format(c_name)
for resource in self.list():
print self.resource_str(resource)
class SwiftResources(Resources):
def __init__(self, session):
super(SwiftResources, self).__init__(session)
self.endpoint = self.session.get_endpoint("object-store")
self.token = self.session.token
# This method is used to retrieve Objects as well as Containers.
def list_containers(self):
containers = swift_client.get_account(self.endpoint, self.token)[1]
return (cont['name'] for cont in containers)
class SwiftObjects(SwiftResources):
def list(self):
swift_objects = []
for cont in self.list_containers():
objs = [{'container': cont, 'name': obj['name']} for obj in
swift_client.get_container(self.endpoint, self.token, cont)[1]]
swift_objects.extend(objs)
return swift_objects
def delete(self, obj):
super(SwiftObjects, self).delete(obj)
swift_client.delete_object(self.endpoint, token=self.token,
container=obj['container'], name=obj['name'])
def resource_str(self, obj):
return "object {} in container {}".format(obj['name'], obj['container'])
class SwiftContainers(SwiftResources):
def list(self):
return self.list_containers()
def delete(self, container):
"""Container must be empty for deletion to succeed."""
super(SwiftContainers, self).delete(container)
swift_client.delete_container(self.endpoint, self.token, container)
def resource_str(self, obj):
return "container {}".format(obj)
class CinderResources(Resources):
def __init__(self, session):
super(CinderResources, self).__init__(session)
# Cinder client library can't use an existing token. When
# using this library, we have to reauthenticate.
self.client = cinder_client.Client(
session.username, session.password,
session.project_name, session.auth_url,
endpoint_type=session.endpoint_type)
class CinderSnapshots(CinderResources):
def list(self):
return self.client.volume_snapshots.list()
def delete(self, snap):
super(CinderResources, self).delete(snap)
self.client.volume_snapshots.delete(snap)
def resource_str(self, snap):
return "snapshot {} (id {})".format(snap.display_name, snap.id)
class CinderVolumes(CinderResources):
def list(self):
return self.client.volumes.list()
def delete(self, vol):
"""Snapshots created from the volume must be deleted first"""
super(CinderVolumes, self).delete(vol)
self.client.volumes.delete(vol)
def resource_str(self, vol):
return "volume {} (id {})".format(vol.display_name, vol.id)
class NeutronResources(Resources):
def __init__(self, session):
super(NeutronResources, self).__init__(session)
self.client = neutron_client.Client(
username=session.username, password=session.password,
tenant_name=session.project_name, auth_url=session.auth_url,
endpoint_type=session.endpoint_type)
self.project_id = session.project_id
# This method is used for routers and interfaces removal
def list_routers(self):
return filter(self._owned_resource, self.client.list_routers()['routers'])
def _owned_resource(self, res):
# Only considering resources owned by project
return res['tenant_id'] == self.project_id
class NeutronRouters(NeutronResources):
def list(self):
return self.list_routers()
def delete(self, router):
"""interfaces must be deleted first"""
super(NeutronRouters, self).delete(router)
# Remove router gateway prior to remove the router itself
self.client.remove_gateway_router(router['id'])
self.client.delete_router(router['id'])
def resource_str(self, router):
return "router {} (id {})".format(router['name'], router['id'])
class NeutronInterfaces(NeutronResources):
def list(self):
def get_ports(router):
# Only considering "router_interface" ports (not gateways)
ports = [port for port in
self.client.list_ports(device_id=router['id'])['ports']
if port["device_owner"] == "network:router_interface"]
return [{'router_id': router['id'], 'interface_id': port['id']}
for port in ports ]
interfaces = [get_ports(rout) for rout in self.list_routers()]
return itertools.chain(*interfaces)
def delete(self, interface):
super(NeutronInterfaces, self).delete(interface)
self.client.remove_interface_router(interface['router_id'],
{'port_id':interface['interface_id']})
def resource_str(self, interface):
return "interfaces {} (id)".format(interface['interface_id'])
class NeutronNetworks(NeutronResources):
def list(self):
return filter(self._owned_resource,
self.client.list_networks()['networks'])
def delete(self, net):
"""
Interfaces connected to the network must be deleted first.
Implying there must not be any VM on the network.
"""
super(NeutronNetworks, self).delete(net)
self.client.delete_network(net['id'])
def resource_str(self, net):
return "network {} (id {})".format(net['name'], net['id'])
class NeutronSecgroups(NeutronResources):
def list(self):
return filter(self._owned_resource,
self.client.list_security_groups()['security_groups'])
def delete(self, secgroup):
"""VMs using the security group should be deleted first"""
super(NeutronSecgroups, self).delete(secgroup)
self.client.delete_security_group(secgroup['id'])
def resource_str(self, secgroup):
return "security group {} (id {})".format(
secgroup['name'], secgroup['id'])
class NeutronFloatingIps(NeutronResources):
def list(self):
return filter(self._owned_resource,
self.client.list_floatingips()['floatingips'])
def delete(self, floating_ip):
super(NeutronFloatingIps, self).delete(floating_ip)
self.client.delete_floatingip(floating_ip['id'])
def resource_str(self, floating_ip):
return "floating ip {} (id {})".format(
floating_ip['floating_ip_address'], floating_ip['id'])
class NovaServers(Resources):
def __init__(self, session):
super(NovaServers, self).__init__(session)
self.client = nova_client.Client(
session.username, session.password,
session.project_name, auth_url=session.auth_url,
endpoint_type=session.endpoint_type)
self.project_id = session.project_id
"""Manage nova resources"""
def list(self):
return self.client.servers.list()
def delete(self, server):
super(NovaServers, self).delete(server)
self.client.servers.delete(server)
def resource_str(self, server):
return "server {} (id {})".format(server.name, server.id)
class GlanceImages(Resources):
def __init__(self, session):
self.client = glance_client.Client(
endpoint=session.get_endpoint("image"),
token=session.token)
self.project_id = session.project_id
def list(self):
return filter(self._owned_resource, self.client.images.list())
def delete(self, image):
super(GlanceImages, self).delete(image)
self.client.images.delete(image.id)
def resource_str(self, image):
return "image {} (id {})".format(image.name, image.id)
def _owned_resource(self, res):
# Only considering resources owned by project
return res.owner == self.project_id
class CeilometerAlarms(Resources):
def __init__(self, session):
# Ceilometer Client needs a method that returns the token
def get_token():
return session.token
self.client = ceilometer_client.Client(
endpoint=session.get_endpoint("metering"),
token=get_token)
self.project_id = session.project_id
def list(self):
query = [{'field': 'project_id',
'op': 'eq',
'value': self.project_id}]
return self.client.alarms.list(q=query)
def delete(self, alarm):
super(CeilometerAlarms, self).delete(alarm)
self.client.alarms.delete(alarm.alarm_id)
def resource_str(self, alarm):
return "alarm {}".format(alarm.name)
class KeystoneManager(object):
"""Manages Keystone queries"""
def __init__(self, username, password, project, auth_url):
self.client = keystone_client.Client(
username=username, password=password,
tenant_name=project, auth_url=auth_url)
def become_project_admin(self, project_name):
user_id = self.client.user_id
logging.info("* Granting role admin to user {} on project {}.".format(
user_id, project_name))
try:
tenants = self.client.tenants.list()
except Exception as e:
raise ConnectionFailed(str(e))
try:
project_id = filter(lambda x : x.name==project_name, tenants)[0].id
except:
raise NoSuchProject(project_name)
roles = self.client.roles.list()
role_id = filter(lambda x : x.name=="admin", roles)[0].id
try:
return self.client.roles.add_user_role(user_id, role_id, project_id)
except api_exceptions.Conflict:
# user is already admin on the target project
pass
def delete_project(self, project_name):
tenants = self.client.tenants.list()
project_id = filter(lambda x : x.name==project_name, tenants)[0].id
logging.info("* Deleting project {}.".format(project_name))
self.client.tenants.delete(project_id)
def purge_project(admin_name, password, project, auth_url,
endpoint_type='publicURL'):
"""
project is the project that will be purged.
Warning: admin must have access to the project.
"""
session = Session(admin_name, password, project,
auth_url, endpoint_type)
for rc in RESOURCES_CLASSES:
try:
resources = globals()[rc](session)
except EndpointNotFound:
# If service is not in Keystone's services catalog, ignoring it
pass
else:
resources.purge()
def list_resources(admin_name, password, project, auth_url,
endpoint_type='publicURL'):
"""
Listing resources of given project.
"""
session = Session(admin_name, password, project,
auth_url, endpoint_type)
for rc in RESOURCES_CLASSES:
try:
resources = globals()[rc](session)
except EndpointNotFound:
pass
else:
resources.dump()
# From Russell Heilling
# http://stackoverflow.com/questions/10551117/setting-options-from-environment-variables-when-using-argparse
class EnvDefault(argparse.Action):
def __init__(self, envvar, required=True, default=None, **kwargs):
# Overriding default with environment variable if available
if envvar in os.environ:
default = os.environ[envvar]
if required and default:
required = False
super(EnvDefault, self).__init__(default=default, required=required,
**kwargs)
def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, values)
def parse_args():
desc = "Purge resources from an Openstack project."
parser = argparse.ArgumentParser(description=desc)
parser.add_argument("--verbose", action="store_true",
dest="verbose",
help="Makes output verbose")
parser.add_argument("--dry_run", action="store_true",
dest="dry_run",
help="List project's resources")
parser.add_argument("--endpoint_type", action=EnvDefault,
envvar='OS_ENDPOINT_TYPE', default="publicURL",
help="Endpoint type to use. Defaults to " \
"env[OS_ENDPOINT_TYPE] or publicURL")
parser.add_argument("--username", action=EnvDefault,
envvar='OS_USERNAME', required=True,
help="A user name with access to the " \
"project being purged. Defaults " \
"to env[OS_USERNAME]")
parser.add_argument("--password", action=EnvDefault,
envvar='OS_PASSWORD', required=True,
help="The user's password. Defaults "
"to env[OS_PASSWORD].")
parser.add_argument("--admin_project", action=EnvDefault,
envvar='OS_TENANT_NAME', required=True,
help="Name of a project the user is admin on. "\
"Defaults to env[OS_TENANT_NAME].")
parser.add_argument("--auth_url", action=EnvDefault,
envvar='OS_AUTH_URL', required=True,
help="Authentication URL. Defaults to " \
"env[OS_AUTH_URL].")
parser.add_argument("--cleanup_project", required=True,
help="Name of project to purge")
return parser.parse_args()
def main():
args = parse_args()
if args.verbose:
logging.basicConfig(level=logging.INFO)
else:
# Set default log level to Warning
logging.basicConfig(level=logging.WARNING)
try:
keystone_manager = KeystoneManager(args.username, args.password,
args.admin_project, args.auth_url)
except api_exceptions.Unauthorized as exc:
print "Authentication failed: {}".format(str(exc))
sys.exit(AUTHENTICATION_FAILED_ERROR_CODE)
try:
keystone_manager.become_project_admin(args.cleanup_project)
except NoSuchProject as exc:
print "Project {} doesn't exist".format(str(exc))
sys.exit(NoSuchProject.ERROR_CODE)
except ConnectionFailed as exc:
print "Connection failed: {}".format(str(exc))
sys.exit(ConnectionFailed.ERROR_CODE)
try:
if args.dry_run:
list_resources(args.username, args.password, args.cleanup_project,
args.auth_url, args.endpoint_type)
else:
purge_project(args.username, args.password, args.cleanup_project,
args.auth_url, args.endpoint_type)
except ConnectionError as exc:
print "Connection error: {}".format(str(exc))
sys.exit(CONNECTION_ERROR_CODE)
except DeletionFailed as exc:
print "Deletion of {} failed".format(str(exc))
sys.exit(DeletionFailed.ERROR_CODE)
if args.dry_run is False:
keystone_manager.delete_project(args.cleanup_project)
sys.exit(0)
if __name__ == "__main__":
main()

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,641 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
#
# Copyright © 2014 Cloudwatt
#
# 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.
TOKEN_ID = '04c7d5ffaeef485f9dc69c06db285bdb'
USER_ID = 'c4da488862bd435c9e6c0275a0d0e49a'
PROJECT_ID = '225da22d3ce34b15877ea70b2a575f58'
VOLUME_PUBLIC_ENDPOINT = 'http://public:8776/v1/225da22d3ce34b15877ea70b2a575f58'
IMAGE_PUBLIC_ENDPOINT = 'http://public:9292'
STORAGE_PUBLIC_ENDPOINT = 'http://public:8080/v1/AUTH_ee5b90900a4b4e85938b0ceadf4467f8'
NETWORK_PUBLIC_ENDPOINT = 'https://network0.cw-labs.net'
COMPUTE_PUBLIC_ENDPOINT = 'https://compute0.cw-labs.net/v2/43c9e28327094e1b81484f4b9aee74d5'
METERING_PUBLIC_ENDPOINT = 'https://metric0.cw-labs.net'
VOLUME_INTERNAL_ENDPOINT = 'http://internal:8776/v1/225da22d3ce34b15877ea70b2a575f58'
IMAGE_INTERNAL_ENDPOINT = 'http://internal:9292'
STORAGE_INTERNAL_ENDPOINT = 'http://internal:8080/v1/AUTH_ee5b90900a4b4e85938b0ceadf4467f8'
NETWORK_INTERNAL_ENDPOINT = 'http://neutron.usr.lab0.aub.cw-labs.net:9696'
COMPUTE_INTERNAL_ENDPOINT = 'http://nova.usr.lab0.aub.cw-labs.net:8774/v2/43c9e28327094e1b81484f4b9aee74d5'
METERING_INTERNAL_ENDPOINT = 'http://ceilometer.usr.lab0.aub.cw-labs.net:8777'
STORAGE_CONTAINERS = ['janeausten', 'marktwain']
STORAGE_OBJECTS = [{'container': 'janeausten', 'name': 'foo'},
{'container': 'janeausten', 'name': 'bar'},
{'container': 'marktwain', 'name': 'hello world'}]
VOLUMES_IDS = ["45baf976-c20a-4894-a7c3-c94b7376bf55", "5aa119a8-d25b-45a7-8d1b-88e127885635"]
SNAPSHOTS_IDS = ["3fbbcccf-d058-4502-8844-6feeffdf4cb5", "e479997c-650b-40a4-9dfe-77655818b0d2"]
ROUTERS_IDS = ["7177abc4-5ae9-4bb7-b0d4-89e94a4abf3b", "a9254bdb-2613-4a13-ac4c-adc581fba50d"]
PORTS_IDS = ["d7815f5b-a228-47bb-a5e5-f139c4e476f6"]
NETWORKS_IDS = ["9d83c053-b0a4-4682-ae80-c00df269ce0a", "ebda9658-093b-41ba-80ce-0cf8cb8365d4"]
SECGROUPS_IDS = ["85cc3048-abc3-43cc-89b3-377341426ac5"]
FLOATING_IPS_IDS = ["2f245a7b-796b-4f26-9cf9-9e82d248fda7", "61cea855-49cb-4846-997d-801b70c71bdd"]
SERVERS_IDS = ["616fb98f-46ca-475e-917e-2563e5a8cd19"]
IMAGES_IDS = ["37717f53-3707-49b9-9dd0-fd063e6b9fc5", "4e150966-cbe7-4fd7-a964-41e008d20f10",
"482fbcc3-d831-411d-a073-ddc828a7a9ed"]
ALARMS_IDS = ["ca950223-e982-4552-9dec-5dc5d3ea4172"]
# Simulating JSON sent from the Server
PROJECT_SCOPED_TOKEN = {
'access': {
'serviceCatalog':
[{
'endpoints': [{
'adminURL': 'http://admin:8776/v1/225da22d3ce34b15877ea70b2a575f58',
'internalURL': VOLUME_INTERNAL_ENDPOINT,
'publicURL': VOLUME_PUBLIC_ENDPOINT,
'region': 'RegionOne'}],
'endpoints_links': [],
'name': 'Volume Service',
'type': 'volume'
}, {
'endpoints': [{
'adminURL': 'http://admin:9292/v1',
'internalURL': IMAGE_INTERNAL_ENDPOINT,
'publicURL': IMAGE_PUBLIC_ENDPOINT,
'region': 'RegionOne'}],
'endpoints_links': [],
'name': 'Image Service',
'type': 'image'
}, {
'endpoints': [{
'adminURL': 'http://admin:8774/v2/225da22d3ce34b15877ea70b2a575f58',
'internalURL': COMPUTE_INTERNAL_ENDPOINT,
'publicURL': COMPUTE_PUBLIC_ENDPOINT,
'region': 'RegionOne'}],
'endpoints_links': [],
'name': 'Compute Service',
'type': 'compute'
}, {
'endpoints': [{
'adminURL': 'http://admin:8773/services/Admin',
'internalURL': 'http://internal:8773/services/Cloud',
'publicURL': 'http://public:8773/services/Cloud',
'region': 'RegionOne'}],
'endpoints_links': [],
'name': 'EC2 Service',
'type': 'ec2'
}, {
'endpoints': [{
'adminURL': 'http://admin:35357/v2.0',
'internalURL': 'http://internal:5000/v2.0',
'publicURL': 'http://public:5000/v2.0',
'region': 'RegionOne'}],
'endpoints_links': [],
'name': 'Identity Service',
'type': 'identity'
}, {
'endpoints': [{
'adminURL': 'http://admin:8080',
'internalURL': STORAGE_INTERNAL_ENDPOINT,
'publicURL': STORAGE_PUBLIC_ENDPOINT,
'region': 'RegionOne'}],
'endpoints_links': [],
'name': 'Object Storage Service',
'type': 'object-store'
}, {
'endpoints': [{
'adminURL': 'http://neutron.usr.lab0.aub.cw-labs.net:9696',
'internalURL': NETWORK_INTERNAL_ENDPOINT,
'publicURL': NETWORK_PUBLIC_ENDPOINT,
'region': 'RegionOne'}],
'endpoints_links': [],
'name': 'Network Service',
'type': 'network'
}, {
'endpoints': [{
'adminURL': 'http://ceilometer.usr.lab0.aub.cw-labs.net:8777',
'internalURL': METERING_INTERNAL_ENDPOINT,
'publicURL': METERING_PUBLIC_ENDPOINT,
'region': 'RegionOne'}],
'endpoints_links': [],
'name': 'Metering service',
'type': 'metering'
}],
'token': {
'expires': '2012-10-03T16:53:36Z',
'id': TOKEN_ID,
'tenant': {
'description': '',
'enabled': True,
'id': PROJECT_ID,
'name': 'exampleproject'
}
},
'user': {
'id': USER_ID,
'name': 'exampleuser',
'roles': [{
'id': 'edc12489faa74ee0aca0b8a0b4d74a74',
'name': 'Member'}],
'roles_links': [],
'username': 'exampleuser'
}
}
}
STORAGE_CONTAINERS_LIST = [
{
"count": 0,
"bytes": 0,
"name": STORAGE_CONTAINERS[0]
},
{
"count": 1,
"bytes": 14,
"name": STORAGE_CONTAINERS[1]
}
]
STORAGE_OBJECTS_LIST_0 = [
{
"hash":"451e372e48e0f6b1114fa0724aa79fa1",
"last_modified":"2014-01-15T16:41:49.390270",
"bytes":14,
"name":STORAGE_OBJECTS[0]['name'],
"content_type":"application/octet-stream"
},
{
"hash":"ed076287532e86365e841e92bfc50d8c",
"last_modified":"2014-01-15T16:37:43.427570",
"bytes":12,
"name":STORAGE_OBJECTS[1]['name'],
"content_type":"application/octet-stream"
}
]
STORAGE_OBJECTS_LIST_1 = [
{
"hash":"451e372e48e0f6b1114fa0724aa7AAAA",
"last_modified":"2014-01-15T16:41:49.390270",
"bytes":14,
"name":STORAGE_OBJECTS[2]['name'],
"content_type":"application/octet-stream"
}
]
VOLUMES_LIST = {
"volumes": [
{
"attachments": [],
"availability_zone": "nova",
"bootable": "false",
"created_at": "2014-02-03T14:22:52.000000",
"display_description": None,
"display_name": "toto",
"id": VOLUMES_IDS[0],
"metadata": {},
"size": 1,
"snapshot_id": None,
"source_volid": None,
"status": "available",
"volume_type": "None"
},
{
"attachments": [],
"availability_zone": "nova",
"bootable": "true",
"created_at": "2014-02-03T14:18:34.000000",
"display_description": "",
"display_name": "CirrOS v0.3.0",
"id": VOLUMES_IDS[1],
"metadata": {},
"size": 1,
"snapshot_id": None,
"source_volid": None,
"status": "available",
"volume_type": "None"
}
]
}
SNAPSHOTS_LIST = {
"snapshots": [
{
"id": SNAPSHOTS_IDS[0],
"display_name": "snap-001",
"display_description": "Daily backup",
"volume_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c",
"status": "available",
"size": 10,
"created_at": "2012-02-29T03:50:07Z"
},
{
"id": SNAPSHOTS_IDS[1],
"display_name": "snap-002",
"display_description": "Weekly backup",
"volume_id": "76b8950a-8594-4e5b-8dce-0dfa9c696358",
"status": "available",
"size": 25,
"created_at": "2012-03-19T01:52:47Z"
}
]
}
ROUTERS_LIST = {
"routers": [{
"status": "ACTIVE",
"external_gateway_info":
{"network_id": "3c5bcddd-6af9-4e6b-9c3e-c153e521cab8"},
"name": "second_routers",
"admin_state_up": True,
"tenant_id": PROJECT_ID,
"id": ROUTERS_IDS[0]
}, {
"status": "ACTIVE",
"external_gateway_info":
{"network_id": "3c5bcddd-6af9-4e6b-9c3e-c153e521cab8"},
"name": "router1",
"admin_state_up": True,
"tenant_id": PROJECT_ID,
"id": ROUTERS_IDS[1]
}, {
"status": "ACTIVE",
"external_gateway_info":
{"network_id": "3c5bcddd-6af9-4e6b-9c3e-c153e521cab8"},
"name": "another_router",
"admin_state_up": True,
"tenant_id": "6b96ff0cb17a4b859e1e575d221683d3",
"id": "7177abc4-5ae9-4bb7-b0d4-89e94a4abf3b"
}]
}
ROUTER_CLEAR_GATEWAY = {
"router": {
"status": "ACTIVE",
"external_gateway_info": None,
"name": "second_routers",
"admin_state_up": True,
"tenant_id": PROJECT_ID,
"id": ROUTERS_IDS[0]
}
}
ROUTER0_PORTS = {
"ports":[
{
"status":"ACTIVE",
"name":"",
"admin_state_up":True,
"network_id":"ebda9658-093b-41ba-80ce-0cf8cb8365d4",
"tenant_id":"63878e4c5dd649d2a980e37aefddfa87",
"binding:vif_type":"ovs",
"device_owner":"compute:None",
"binding:capabilities":{
"port_filter":False
},
"mac_address":"fa:16:3e:b9:ef:05",
"fixed_ips":[
{
"subnet_id":"aca4d43c-c48c-4a2c-9bb6-ba374ef7e135",
"ip_address":"172.24.4.227"
}
],
"id": "664ebd1a-facd-4c20-948c-07a784475ab0",
"device_id": ROUTERS_IDS[0]
}
]
}
ROUTER1_PORTS = {
"ports":[
{
"status":"DOWN",
"name":"",
"admin_state_up":True,
"network_id":"ebda9658-093b-41ba-80ce-0cf8cb8365d4",
"tenant_id":"",
"binding:vif_type":"ovs",
"device_owner":"network:router_gateway",
"binding:capabilities":{
"port_filter":False
},
"mac_address":"fa:16:3e:4a:3a:a2",
"fixed_ips":[
{
"subnet_id":"aca4d43c-c48c-4a2c-9bb6-ba374ef7e135",
"ip_address":"172.24.4.226"
}
],
"id": "c5ca7017-c390-4ccc-8cd7-333747e57fef",
"device_id": ROUTERS_IDS[1]
},
{
"status":"ACTIVE",
"name":"",
"admin_state_up":True,
"network_id":"9d83c053-b0a4-4682-ae80-c00df269ce0a",
"tenant_id":"625887121e364204873d362b553ab171",
"binding:vif_type":"ovs",
"device_owner":"network:router_interface",
"binding:capabilities":{
"port_filter":False
},
"mac_address":"fa:16:3e:2d:dc:7e",
"fixed_ips":[
{
"subnet_id":"a318fcb4-9ff0-4485-b78c-9e6738c21b26",
"ip_address":"10.0.0.1"
}
],
"id": PORTS_IDS[0],
"device_id": ROUTERS_IDS[1]
}
]
}
REMOVE_ROUTER_INTERFACE = {
"id": "8604a0de-7f6b-409a-a47c-a1cc7bc77b2e",
"tenant_id": "2f245a7b-796b-4f26-9cf9-9e82d248fda7",
"port_id": "3a44f4e5-1694-493a-a1fb-393881c673a4",
"subnet_id": "a2f1f29d-571b-4533-907f-5803ab96ead1"
}
NETWORKS_LIST = {
"networks": [
{
"status": "ACTIVE",
"subnets": ["a318fcb4-9ff0-4485-b78c-9e6738c21b26"],
"name": "private",
"admin_state_up": True,
"tenant_id": PROJECT_ID,
"id": NETWORKS_IDS[0],
"shared": False
},
{
"status": "ACTIVE",
"subnets": ["aca4d43c-c48c-4a2c-9bb6-ba374ef7e135"],
"name": "nova",
"admin_state_up": True,
"tenant_id": PROJECT_ID,
"id": NETWORKS_IDS[1],
"shared": False
},
{
"status": "ACTIVE",
"subnets": ["e12f0c45-46e3-446a-b207-9474b27687a6"],
"name": "network_3",
"admin_state_up": True,
"tenant_id": "ed680f49ff714162ab3612d7876ffce5",
"id": "afc75773-640e-403c-9fff-62ba98db1f19",
"shared": True
}
]
}
SECGROUPS_LIST = {
"security_groups":[
{
"description":"default",
"id":"85cc3048-abc3-43cc-89b3-377341426ac5",
"name":"default",
"security_group_rules":[
{
"direction":"egress",
"ethertype":"IPv6",
"id":"3c0e45ff-adaf-4124-b083-bf390e5482ff",
"port_range_max":None,
"port_range_min":None,
"protocol":None,
"remote_group_id":None,
"remote_ip_prefix":None,
"security_group_id":"85cc3048-abc3-43cc-89b3-377341426ac5",
"tenant_id": PROJECT_ID
},
{
"direction":"egress",
"ethertype":"IPv4",
"id":"93aa42e5-80db-4581-9391-3a608bd0e448",
"port_range_max":None,
"port_range_min":None,
"protocol":None,
"remote_group_id":None,
"remote_ip_prefix":None,
"security_group_id":"85cc3048-abc3-43cc-89b3-377341426ac5",
"tenant_id": PROJECT_ID
},
{
"direction":"ingress",
"ethertype":"IPv6",
"id":"c0b09f00-1d49-4e64-a0a7-8a186d928138",
"port_range_max":None,
"port_range_min":None,
"protocol":None,
"remote_group_id":"85cc3048-abc3-43cc-89b3-377341426ac5",
"remote_ip_prefix":None,
"security_group_id":"85cc3048-abc3-43cc-89b3-377341426ac5",
"tenant_id": PROJECT_ID
},
{
"direction":"ingress",
"ethertype":"IPv4",
"id":"f7d45c89-008e-4bab-88ad-d6811724c51c",
"port_range_max":None,
"port_range_min":None,
"protocol":None,
"remote_group_id":"85cc3048-abc3-43cc-89b3-377341426ac5",
"remote_ip_prefix":None,
"security_group_id":"85cc3048-abc3-43cc-89b3-377341426ac5",
"tenant_id": PROJECT_ID
}
],
"tenant_id": PROJECT_ID
}
]
}
FLOATING_IPS_LIST = {
"floatingips":
[
{
"router_id": "d23abc8d-2991-4a55-ba98-2aaea84cc72f",
"tenant_id": PROJECT_ID,
"floating_network_id": "376da547-b977-4cfe-9cba-275c80debf57",
"fixed_ip_address": "10.0.0.3",
"floating_ip_address": "172.24.4.228",
"port_id": "ce705c24-c1ef-408a-bda3-7bbd946164ab",
"id": FLOATING_IPS_IDS[0]
},
{
"router_id": None,
"tenant_id": PROJECT_ID,
"floating_network_id": "376da547-b977-4cfe-9cba-275c80debf57",
"fixed_ip_address": None,
"floating_ip_address": "172.24.4.227",
"port_id": None,
"id": FLOATING_IPS_IDS[1]
}
]
}
SERVERS_LIST = {
"servers": [
{
"accessIPv4": "",
"accessIPv6": "",
"addresses": {
"private": [
{
"addr": "192.168.0.3",
"version": 4
}
]
},
"created": "2012-09-07T16:56:37Z",
"flavor": {
"id": "1",
"links": [
{
"href": "http://openstack.example.com/openstack/flavors/1",
"rel": "bookmark"
}
]
},
"hostId": "16d193736a5cfdb60c697ca27ad071d6126fa13baeb670fc9d10645e",
"id": SERVERS_IDS[0],
"image": {
"id": "70a599e0-31e7-49b7-b260-868f441e862b",
"links": [
{
"href": "http://openstack.example.com/openstack/images/70a599e0-31e7-49b7-b260-868f441e862b",
"rel": "bookmark"
}
]
},
"links": [
{
"href": "http://openstack.example.com/v2/openstack/servers/05184ba3-00ba-4fbc-b7a2-03b62b884931",
"rel": "self"
},
{
"href": "http://openstack.example.com/openstack/servers/05184ba3-00ba-4fbc-b7a2-03b62b884931",
"rel": "bookmark"
}
],
"metadata": {
"My Server Name": "Apache1"
},
"name": "new-server-test",
"progress": 0,
"status": "ACTIVE",
"tenant_id": "openstack",
"updated": "2012-09-07T16:56:37Z",
"user_id": "fake"
}
]
}
IMAGES_LIST = {
"images": [
{
"checksum": "f8a2eeee2dc65b3d9b6e63678955bd83",
"container_format": "ami",
"created_at": "2014-02-03T14:13:53",
"deleted": False,
"deleted_at": None,
"disk_format": "ami",
"id": "37717f53-3707-49b9-9dd0-fd063e6b9fc5",
"is_public": True,
"min_disk": 0,
"min_ram": 0,
"name": "cirros-0.3.1-x86_64-uec",
"owner": PROJECT_ID,
"properties": {
"kernel_id": "4e150966-cbe7-4fd7-a964-41e008d20f10",
"ramdisk_id": "482fbcc3-d831-411d-a073-ddc828a7a9ed"
},
"protected": False,
"size": 25165824,
"status": "active",
"updated_at": "2014-02-03T14:13:54"
},
{
"checksum": "c352f4e7121c6eae958bc1570324f17e",
"container_format": "aki",
"created_at": "2014-02-03T14:13:52",
"deleted": False,
"deleted_at": None,
"disk_format": "aki",
"id": "4e150966-cbe7-4fd7-a964-41e008d20f10",
"is_public": True,
"min_disk": 0,
"min_ram": 0,
"name": "cirros-0.3.1-x86_64-uec-kernel",
"owner": PROJECT_ID,
"properties": {},
"protected": False,
"size": 4955792,
"status": "active",
"updated_at": "2014-02-03T14:13:52"
},
{
"checksum": "69c33642f44ca552ba4bb8b66ad97e85",
"container_format": "ari",
"created_at": "2014-02-03T14:13:53",
"deleted": False,
"deleted_at": None,
"disk_format": "ari",
"id": "482fbcc3-d831-411d-a073-ddc828a7a9ed",
"is_public": True,
"min_disk": 0,
"min_ram": 0,
"name": "cirros-0.3.1-x86_64-uec-ramdisk",
"owner": PROJECT_ID,
"properties": {},
"protected": False,
"size": 3714968,
"status": "active",
"updated_at": "2014-02-03T14:13:53"
}
]
}
ALARMS_LIST = [
{
"alarm_actions": [
"http://site:8000/alarm"
],
"alarm_id": ALARMS_IDS[0],
"combination_rule": None,
"description": "An alarm",
"enabled": True,
"insufficient_data_actions": [
"http://site:8000/nodata"
],
"name": "SwiftObjectAlarm",
"ok_actions": [
"http://site:8000/ok"
],
"project_id": "c96c887c216949acbdfbd8b494863567",
"repeat_actions": False,
"state": "ok",
"state_timestamp": "2013-11-21T12:33:08.486228",
"threshold_rule": None,
"timestamp": "2013-11-21T12:33:08.486221",
"type": "threshold",
"user_id": "c96c887c216949acbdfbd8b494863567"
}
]

View File

@ -0,0 +1,431 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
#
# This software is released under the MIT License.
#
# Copyright (c) 2014 Cloudwatt
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import itertools
import json as jsonutils
import httpretty
import testtools
import client_fixtures
from ospurge import os_purge
USERNAME = "username"
PASSWORD = "password"
PROJECT_NAME = "project"
AUTH_URL = "http://localhost:5000/v2.0"
class HttpTest(testtools.TestCase):
def stub_url(self, method, parts=None, base_url=None, json=None, **kwargs):
if not base_url:
base_url = self.TEST_URL
if json is not None:
kwargs['body'] = jsonutils.dumps(json)
kwargs['content_type'] = 'application/json'
if parts:
url = '/'.join([p.strip('/') for p in [base_url] + parts])
else:
url = base_url
httpretty.register_uri(method, url, **kwargs)
def stub_auth(self):
self.stub_url('POST', parts=['tokens'], base_url=AUTH_URL,
json=client_fixtures.PROJECT_SCOPED_TOKEN)
class SessionTest(HttpTest):
@httpretty.activate
def test_init(self):
self.stub_auth()
session = os_purge.Session(USERNAME, PASSWORD,
PROJECT_NAME, AUTH_URL)
self.assertEqual(session.token, client_fixtures.TOKEN_ID)
self.assertEqual(session.user_id, client_fixtures.USER_ID)
self.assertEqual(session.project_id, client_fixtures.PROJECT_ID)
@httpretty.activate
def test_get_public_endpoint(self):
self.stub_auth()
session = os_purge.Session(USERNAME, PASSWORD,
PROJECT_NAME, AUTH_URL)
endpoint = session.get_endpoint('volume')
self.assertEqual(endpoint, client_fixtures.VOLUME_PUBLIC_ENDPOINT)
endpoint = session.get_endpoint('image')
self.assertEqual(endpoint, client_fixtures.IMAGE_PUBLIC_ENDPOINT)
@httpretty.activate
def test_get_internal_endpoint(self):
self.stub_auth()
session = os_purge.Session(USERNAME, PASSWORD, PROJECT_NAME,
AUTH_URL, endpoint_type='internalURL')
endpoint = session.get_endpoint('volume')
self.assertEqual(endpoint, client_fixtures.VOLUME_INTERNAL_ENDPOINT)
endpoint = session.get_endpoint('image')
self.assertEqual(endpoint, client_fixtures.IMAGE_INTERNAL_ENDPOINT)
# Abstract class
class TestResourcesBase(HttpTest):
"""
Creates a session object that can be used to test any service.
"""
@httpretty.activate
def setUp(self):
super(TestResourcesBase, self).setUp()
self.stub_auth()
self.session = os_purge.Session(USERNAME, PASSWORD,
PROJECT_NAME, AUTH_URL)
@httpretty.activate
def _test_list(self):
self.stub_auth()
self.stub_list()
elts = list(self.resources.list())
# Some Openstack resources use attributes, while others use dicts
try:
ids = [elt.id for elt in elts]
except AttributeError:
ids = [elt['id'] for elt in elts]
self.assertEqual(self.IDS, ids)
@httpretty.activate
def _test_delete(self):
self.stub_auth()
self.stub_list()
self.stub_delete()
elts = self.resources.list()
# List() must return an iterable
res = itertools.islice(elts, 1).next()
self.resources.delete(res) # Checks this doesn't raise an exception
class TestSwiftBase(TestResourcesBase):
TEST_URL = client_fixtures.STORAGE_PUBLIC_ENDPOINT
class TestSwiftResources(TestSwiftBase):
@httpretty.activate
def test_list_containers(self):
self.stub_url('GET', json=client_fixtures.STORAGE_CONTAINERS_LIST)
swift = os_purge.SwiftResources(self.session)
conts = list(swift.list_containers())
self.assertEqual(conts, client_fixtures.STORAGE_CONTAINERS)
class TestSwiftObjects(TestSwiftBase):
def stub_list(self):
self.stub_url('GET', json=client_fixtures.STORAGE_CONTAINERS_LIST)
self.stub_url('GET', parts=[client_fixtures.STORAGE_CONTAINERS[0]],
json=client_fixtures.STORAGE_OBJECTS_LIST_0),
self.stub_url('GET', parts=[client_fixtures.STORAGE_CONTAINERS[1]],
json=client_fixtures.STORAGE_OBJECTS_LIST_1)
def stub_delete(self):
for obj in client_fixtures.STORAGE_OBJECTS:
self.stub_url('DELETE', parts=[obj['container'], obj['name']])
def setUp(self):
super(TestSwiftObjects, self).setUp()
self.resources = os_purge.SwiftObjects(self.session)
@httpretty.activate
def test_list(self):
self.stub_list()
objs = list(self.resources.list())
self.assertEqual(client_fixtures.STORAGE_OBJECTS, objs)
def test_delete(self):
self._test_delete()
class TestSwiftContainers(TestSwiftBase):
def stub_list(self):
self.stub_url('GET', json=client_fixtures.STORAGE_CONTAINERS_LIST)
def stub_delete(self):
self.stub_url('DELETE', parts=[client_fixtures.STORAGE_CONTAINERS[0]])
def setUp(self):
super(TestSwiftContainers, self).setUp()
self.resources = os_purge.SwiftContainers(self.session)
@httpretty.activate
def test_list(self):
self.stub_list()
conts = list(self.resources.list())
self.assertEqual(conts, client_fixtures.STORAGE_CONTAINERS)
def test_delete(self):
self._test_delete()
class TestCinderBase(TestResourcesBase):
TEST_URL = client_fixtures.VOLUME_PUBLIC_ENDPOINT
class TestCinderSnapshots(TestCinderBase):
IDS = client_fixtures.SNAPSHOTS_IDS
def stub_list(self):
self.stub_url('GET', parts=['snapshots', 'detail'],
json=client_fixtures.SNAPSHOTS_LIST)
def stub_delete(self):
self.stub_url('DELETE', parts=['snapshots', client_fixtures.SNAPSHOTS_IDS[0]])
def setUp(self):
super(TestCinderSnapshots, self).setUp()
self.resources = os_purge.CinderSnapshots(self.session)
def test_list(self):
self._test_list()
def test_delete(self):
self._test_delete()
class TestCinderVolumes(TestCinderBase):
IDS = client_fixtures.VOLUMES_IDS
def stub_list(self):
self.stub_url('GET', parts=['volumes', 'detail'],
json=client_fixtures.VOLUMES_LIST)
def stub_delete(self):
self.stub_url('DELETE', parts=['volumes', client_fixtures.VOLUMES_IDS[0]])
def setUp(self):
super(TestCinderVolumes, self).setUp()
self.resources = os_purge.CinderVolumes(self.session)
def test_list(self):
self._test_list()
def test_delete(self):
self._test_delete()
class TestNeutronBase(TestResourcesBase):
TEST_URL = client_fixtures.NETWORK_PUBLIC_ENDPOINT
# Used both in TestNeutronRouters and TestNeutronInterfaces
def stub_list_routers(self):
self.stub_url('GET', parts=['v2.0', 'routers.json'],
json=client_fixtures.ROUTERS_LIST)
class TestNeutronRouters(TestNeutronBase):
IDS = client_fixtures.ROUTERS_IDS
def stub_list(self):
self.stub_list_routers()
def stub_delete(self):
routid = client_fixtures.ROUTERS_IDS[0]
self.stub_url('PUT', parts=['v2.0', 'routers', "%s.json"%routid],
json=client_fixtures.ROUTER_CLEAR_GATEWAY)
self.stub_url('DELETE', parts=['v2.0', 'routers', "%s.json"%routid],
json={})
def setUp(self):
super(TestNeutronRouters, self).setUp()
self.resources = os_purge.NeutronRouters(self.session)
def test_list(self):
self._test_list()
def test_delete(self):
self._test_delete()
class TestNeutronInterfaces(TestNeutronBase):
def stub_list(self):
self.stub_list_routers()
self.stub_url('GET', parts=['v2.0', "ports.json?device_id={}".format(client_fixtures.ROUTERS_IDS[0])],
json=client_fixtures.ROUTER0_PORTS)
self.stub_url('GET', parts=['v2.0', "ports.json?device_id={}".format(client_fixtures.ROUTERS_IDS[1])],
json=client_fixtures.ROUTER1_PORTS)
def stub_delete(self):
for rout_id in client_fixtures.ROUTERS_IDS:
self.stub_url('PUT', parts=['v2.0', 'routers', rout_id,
'remove_router_interface.json'],
json=client_fixtures.REMOVE_ROUTER_INTERFACE)
def setUp(self):
super(TestNeutronInterfaces, self).setUp()
self.resources = os_purge.NeutronInterfaces(self.session)
# Special case there, interfaces ids can be accessed through
# port['interface_id']['id']
@httpretty.activate
def test_list(self):
self.stub_auth()
self.stub_list()
ids = [port['interface_id'] for port in self.resources.list()]
# Converting lists to sets, because order of element may have change
self.assertEqual(set(ids), set(client_fixtures.PORTS_IDS))
def test_delete(self):
self._test_delete()
class TestNeutronNetworks(TestNeutronBase):
IDS = client_fixtures.NETWORKS_IDS
def stub_list(self):
self.stub_url('GET', parts=['v2.0', 'networks.json'],
json=client_fixtures.NETWORKS_LIST)
def stub_delete(self):
for net_id in client_fixtures.NETWORKS_IDS:
self.stub_url('DELETE', parts=['v2.0', 'networks',
"{}.json".format(net_id)], json={})
def setUp(self):
super(TestNeutronNetworks, self).setUp()
self.resources = os_purge.NeutronNetworks(self.session)
def test_list(self):
self._test_list()
def test_delete(self):
self._test_delete()
class TestNeutronSecgroups(TestNeutronBase):
IDS = client_fixtures.SECGROUPS_IDS
def stub_list(self):
self.stub_url('GET', parts=['v2.0', 'security-groups.json'],
json=client_fixtures.SECGROUPS_LIST)
def stub_delete(self):
for secgroup_id in client_fixtures.SECGROUPS_IDS:
self.stub_url('DELETE', parts=['v2.0', 'security-groups',
"{}.json".format(secgroup_id)], json={})
def setUp(self):
super(TestNeutronSecgroups, self).setUp()
self.resources = os_purge.NeutronSecgroups(self.session)
def test_list(self):
self._test_list()
def test_delete(self):
self._test_delete()
class TestNeutronFloatingIps(TestNeutronBase):
IDS = client_fixtures.FLOATING_IPS_IDS
def stub_list(self):
self.stub_url('GET', parts=['v2.0', 'floatingips.json'],
json=client_fixtures.FLOATING_IPS_LIST)
def stub_delete(self):
ip_id = client_fixtures.FLOATING_IPS_IDS[0]
self.stub_url('DELETE', parts=['v2.0', 'floatingips',
"{}.json".format(ip_id)], json={})
def setUp(self):
super(TestNeutronFloatingIps, self).setUp()
self.resources = os_purge.NeutronFloatingIps(self.session)
def test_list(self):
self._test_list()
def test_delete(self):
self._test_delete()
class TestNovaServers(TestResourcesBase):
TEST_URL = client_fixtures.COMPUTE_PUBLIC_ENDPOINT
IDS = client_fixtures.SERVERS_IDS
def stub_list(self):
self.stub_url('GET', parts=['servers', 'detail'],
json=client_fixtures.SERVERS_LIST)
def stub_delete(self):
self.stub_url('DELETE', parts=['servers', client_fixtures.SERVERS_IDS[0]])
def setUp(self):
super(TestNovaServers, self).setUp()
self.resources = os_purge.NovaServers(self.session)
def test_list(self):
self._test_list()
def test_delete(self):
self._test_delete()
class TestGlanceImages(TestResourcesBase):
TEST_URL = client_fixtures.IMAGE_PUBLIC_ENDPOINT
IDS = client_fixtures.IMAGES_IDS
def stub_list(self):
self.stub_url('GET', parts=['v1', 'images', 'detail'],
json=client_fixtures.IMAGES_LIST)
def stub_delete(self):
self.stub_url('DELETE', parts=['v1', 'images', client_fixtures.IMAGES_IDS[0]])
def setUp(self):
super(TestGlanceImages, self).setUp()
self.resources = os_purge.GlanceImages(self.session)
def test_list(self):
self._test_list()
def test_delete(self):
self._test_delete()
class TestCeilometerAlarms(TestResourcesBase):
TEST_URL = client_fixtures.METERING_PUBLIC_ENDPOINT
def stub_list(self):
self.stub_url('GET', parts=['v2', 'alarms'],
json=client_fixtures.ALARMS_LIST)
def stub_delete(self):
self.stub_url('DELETE', parts=['v2', 'alarms', client_fixtures.ALARMS_IDS[0]])
def setUp(self):
super(TestCeilometerAlarms, self).setUp()
self.resources = os_purge.CeilometerAlarms(self.session)
@httpretty.activate
def test_list(self):
self.stub_auth()
self.stub_list()
elts = list(self.resources.list())
ids = [elt.alarm_id for elt in elts]
self.assertEqual(client_fixtures.ALARMS_IDS, ids)
def test_delete(self):
self._test_delete()

7
requirements.txt Normal file
View File

@ -0,0 +1,7 @@
python-ceilometerclient
python-cinderclient
python-glanceclient
python-keystoneclient
python-neutronclient
python-novaclient
python-swiftclient

18
setup.cfg Normal file
View File

@ -0,0 +1,18 @@
[metadata]
name = ospurge
author = Florent Flament
author-email = florent.flament-ext@cloudwatt.com
summary =
description-file = README.md
license = Apache-2
classifier =
keywords =
openstack
[entry_points]
console_scripts =
os_purge = ospurge.os_purge:main
[files]
packages =
ospurge

6
setup.py Normal file
View File

@ -0,0 +1,6 @@
from setuptools import setup
setup(
setup_requires=['pbr'],
pbr=True,
)

4
test-requirements.txt Normal file
View File

@ -0,0 +1,4 @@
httpretty
testtools
nose
requests