Initial commit
This commit is contained in:
commit
98008cb337
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal 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
24
README.md
Normal 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
5
debian/changelog
vendored
Normal 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
1
debian/compat
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
7
|
17
debian/control
vendored
Normal file
17
debian/control
vendored
Normal 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
14
debian/python-resourcesmanager.preinst
vendored
Normal 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
22
debian/rules
vendored
Executable 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
1
debian/source/format
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
1.0
|
1
ospurge/__init__.py
Normal file
1
ospurge/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
588
ospurge/os_purge.py
Executable file
588
ospurge/os_purge.py
Executable 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()
|
||||||
|
|
1
ospurge/tests/__init__.py
Normal file
1
ospurge/tests/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
641
ospurge/tests/client_fixtures.py
Normal file
641
ospurge/tests/client_fixtures.py
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
431
ospurge/tests/test_os_purge.py
Normal file
431
ospurge/tests/test_os_purge.py
Normal 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
7
requirements.txt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
python-ceilometerclient
|
||||||
|
python-cinderclient
|
||||||
|
python-glanceclient
|
||||||
|
python-keystoneclient
|
||||||
|
python-neutronclient
|
||||||
|
python-novaclient
|
||||||
|
python-swiftclient
|
18
setup.cfg
Normal file
18
setup.cfg
Normal 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
6
setup.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
setup(
|
||||||
|
setup_requires=['pbr'],
|
||||||
|
pbr=True,
|
||||||
|
)
|
4
test-requirements.txt
Normal file
4
test-requirements.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
httpretty
|
||||||
|
testtools
|
||||||
|
nose
|
||||||
|
requests
|
Loading…
Reference in New Issue
Block a user