From af05553cdd652c88d49582d147c49d6a7007acd5 Mon Sep 17 00:00:00 2001 From: ecerquei Date: Fri, 30 Apr 2021 09:57:36 -0400 Subject: [PATCH 1/5] janitor image --- docs/source/logo.jpg | Bin 0 -> 29028 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/source/logo.jpg diff --git a/docs/source/logo.jpg b/docs/source/logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..638b46131b48c40b275006d3f3440e3e80b4decf GIT binary patch literal 29028 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≧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

fVAj{2r_Ed6;UU{Aeor^qB4}k^AgJ-rA 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 literal 0 HcmV?d00001 From a9580cb8b2b47a6d89ac07a37e879ca135121fea Mon Sep 17 00:00:00 2001 From: ecerquei Date: Fri, 30 Apr 2021 10:26:09 -0400 Subject: [PATCH 2/5] WIP upgrading to python3 --- AUTHORS | 9 +- Makefile | 4 +- README.md | 154 ++++++++++++++++++---------- janitor.spec.in | 10 +- janitor/cli.py | 4 +- janitor/module/clean.py | 13 +-- janitor/module/history.py | 20 ++-- janitor/provider/openstack.py | 19 ++-- requirements/devel.txt | 8 +- requirements/production.txt | 2 +- tests/README.md | 74 ++++++------- tests/helper.py | 11 +- tests/test_openstack_credentials.py | 4 +- tests/test_openstack_instances.py | 17 ++- tests/test_openstacksdk.py | 2 +- tests/whitelist.txt | 22 +++- 16 files changed, 224 insertions(+), 149 deletions(-) diff --git a/AUTHORS b/AUTHORS index 0461765..90142e0 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,7 +1,6 @@ -All the team members and collaborators -that touched this project somehow will be added to the list -below: +All collaborators with this project: -Team Members: +Collaborators: ------------- -- Eduardo Cerqueira +- Eduardo Cerqueira +- Pavol Kotvan diff --git a/Makefile b/Makefile index a080595..b270942 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ default: help NAME=janitor MAN=janitor.1 -VERSION=0.1 +VERSION=0.2 RPMDIST=$(shell rpm --eval '%dist') #RELEASE=1$(rpmsuffix)$(RPMDIST) RELEASE=1 @@ -18,7 +18,7 @@ help: @echo @echo "clean clean temp files from local workspace" @echo "doc generate sphinx documentation and man pages" - @echo "test run unit tests locally" + @echo "test run functional/unit tests locally" @echo "tarball generate tarball of p roject" @echo "rpm build source codes and generate rpm file" @echo "srpm generate SRPM file" diff --git a/README.md b/README.md index 32f15f6..eb89624 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,49 @@ ![copr build](https://copr.fedorainfracloud.org/coprs/eduardocerqueira/janitor/package/janitor/status_image/last_build.png) -# janitor +# Janitor -MOTIVATION ----------- + -One of my Openstack tenants that I use to run tests for an internal tool has a very limited quota so all the time I need to stop my tests and automations and spend few minutes -cleaning up public/floating ips, destroying virtual machines and others. The idea to have janitor or a butler is actually an old idea and a coworker had implemented -it but in a more sophisticated and fashion way this is a simply version and currently only focusing in Openstack maybe further I'll extend it to AWS, Openshift and others. +Janitor is a Linux component to perform quota management tasks to an [Openstack](https://www.openstack.org/) tenant. -WHAT IS THIS -------------- +It `is not` an official Openstack's tool, and Janitor's code is wrapping Openstack python client libraries: -Linux tool to help clean-up tasks for Openstack. +* [python-openstackclient](https://pypi.org/project/python-openstackclient/) to manage authentication and as general API +* [python-neutronclient](https://github.com/openstack/python-neutronclient) to manage network +* [python-novaclient](https://github.com/openstack/python-novaclient) to manage compute nodes and vms +* [python-glanceclient](https://github.com/openstack/python-glanceclient) to manage cloud images +* [python-cinderclient](https://github.com/openstack/python-cinderclient) to manage volumes and block storage -USAGE +## Table of content +* [Motivation](README.md#motivation) +* [Install and usage](README.md#install-and-usage) +* [Setup your dev environment](README.md#local-dev-environment) +* [Running janitor functional tests](tests/README.md#janitor-tests) +* [Contributing](README.md#contributing) + + +## Motivation + +I have been using OpenStack continuously since 2014 as a resource to run workloads with the main purpose of +testing software. Manage a healthy Openstack tenant quota is vital when working with CI/CD and a large number of +automated pipelines which run 24/7. A long time ago I and my team were having a lot of headaches with one of internal +Openstack the cluster which at that time had a very limited quota and workload capacity but was important for the +test workflow. With the constant false-positive for test failing due to lack of public/floating IP, storage/volume, +and even virtual CPU when provisioning new VMs, we got the idea of implementing Janitor, a super simple script/codes +to `automate the clean-up task of deleting left-over virtual machines as well network and volume from an Openstack +tenant, all managed by API and remote calls.` + + +## Install and Usage + +``` +python janitor/cli.py openstack --openrc /root/insights-qa-openrc.sh --whitelist /var/lib/jenkins/workspace/janitor/janitor_whitelist.txt --keystone v3 +python janitor/cli.py openstack --openrc /home/ecerquei/osp/insights-qa-openrc.sh --whitelist /janitor/janitor_whitelist.txt --keystone v3 +openstack --openrc /home/ecerquei/osp/janitor-openrc.sh --whitelist /home/ecerquei/osp/janitor-whitelist.txt --keystone v3 +``` + +### usage clean up virtual machines and release floating ips for Openstack keep items declared in the whitelist.txt file: @@ -31,6 +59,65 @@ listing history for your janitor: janitor history + +## local dev environment + +**Requirements:** +* Linux OS *tested on Fedora and CentOS* +* Python3 +* packages: + +``` +# install packages needed for build and release RPM, and generate doc +sudo dnf install redhat-rpm-config python3-devel gcc python3-devel python3-pip python3-wheel python3-setuptools, python3-sphinx + +# prep your python env +git clone git@github.com:eduardocerqueira/janitor.git +cd janitor +python3 -m venv venv +source venv/bin/activate +pip install -r requirements/devel.txt + +# check janitor installation +python janitor/cli.py --help +``` + +see [running janitor functional tests](tests/README.md#janitor-tests) + + +or using **make** it requires basic packages in your machine I recommend: python-setuptools, python-sphinx, python3-devel and gcc + +## RPM / Build + + $ make + + Usage: make where is one of + + clean clean temp files from local workspace + doc generate sphinx documentation and man pages + test run functional/unit tests locally + tarball generate tarball of project + rpm build source codes and generate rpm file + srpm generate SRPM file + build generate srpm and send to build in copr + all clean test doc rpm + flake8 check Python style based on flake8 + +Running from your local machine, you can generate your own RPM running: + + $ make rpm + +and if your environment is setup properly you should have your RPM at: /home/user/git/janitor/rpmbuild/RPMS/x86_64/janitor-0.0.1-1.x86_64.rpm + +janitor is being built on Fedora Copr: https://copr.fedorainfracloud.org/coprs/eduardocerqueira/janitor/builds/ + +running a new build you need to check your ~/.config/copr-fedora file and run: + + make build + + + + DEMO ---- @@ -102,54 +189,9 @@ Running the program with parameter to print history file: | 2017-07-13 15:00:57 | deleted | 358be8d1-6d4a-4db7-973f-8369d4ff86f7 | +---------------------+---------+--------------------------------------+ -# For developer and contributers - -Note: - At the moment janitor requires python2.7 and it is not compatible with python3 yet! - -This section describes how to build a new RPM for janitor; - -* Fedora 31 - - ``` - sudo dnf install redhat-rpm-config python-devel gcc python2-devel python-pip python-wheel - virtualenv -p /usr/bin/python2.7 venv - source venv/bin/activate - pip install -r requirements/devel.txt - - # test - python janitor/cli.py --help - ``` - -or using **make** so it requires basic packages in your machine I recommend: python-setuptools, python-sphinx, python-devel and gcc - -## RPM / Build - - $ make - - Usage: make where is one of - - clean clean temp files from local workspace - doc generate sphinx documentation and man pages - test run unit tests locally - tarball generate tarball of project - rpm build source codes and generate rpm file - srpm generate SRPM file - build generate srpm and send to build in copr - all clean test doc rpm - flake8 check Python style based on flake8 +## Contributing -Running from your local machine, you can generate your own RPM running: - $ make rpm - -and if your environment is setup properly you should have your RPM at: /home/user/git/janitor/rpmbuild/RPMS/x86_64/janitor-0.0.1-1.x86_64.rpm - -janitor is being built on Fedora Copr: https://copr.fedorainfracloud.org/coprs/eduardocerqueira/janitor/builds/ - -running a new build you need to check your ~/.config/copr-fedora file and run: - - make build ## install diff --git a/janitor.spec.in b/janitor.spec.in index d9e8cb1..36be160 100644 --- a/janitor.spec.in +++ b/janitor.spec.in @@ -6,9 +6,9 @@ Group: Applications/Tools License: GPL3+ Source0: http://github.com/eduardocerqueira/janitor/%{name}-%{version}.tar.gz -BuildRequires: python-setuptools -Requires: python -Requires: python-pip +BuildRequires: python3-setuptools +Requires: python3 +Requires: python3-pip Requires: python-click Requires: python-prettytable Requires: python-openstackclient @@ -19,7 +19,7 @@ Requires: python-openstackclient %define _missing_doc_files_terminate_build 0 %description -janitor is a Linux helper tool to perform clean-up for Openstack based on whitelist +janitor is a Linux helper tool to perform clean-up tasks to Openstack based on whitelist %prep %setup -q -n %{name} @@ -41,5 +41,7 @@ cp janitor.1 %{buildroot}/%{_mandir}/man1/janitor.1 %{_mandir}/man1/janitor.1.gz %changelog +* Fri Apr 30 2021 Eduardo Cerqueira - 0.0.2 +- code refactoring migrating to python3 * Sun Oct 30 2016 Eduardo Cerqueira - 0.0.1 - initial build diff --git a/janitor/cli.py b/janitor/cli.py index 260825e..780726b 100644 --- a/janitor/cli.py +++ b/janitor/cli.py @@ -43,11 +43,11 @@ def openstack(openrc, whitelist, keystone): vms = openstack.get_all_instances() volumes = openstack.get_volume(status={'status': 'available'}) except Exception as ex: - print ex + print(ex) exit(1) # clean up clean = Clean(vms, whitelist, volumes, openstack) - clean.run() + #clean.run() @click.command(short_help='show janitor history') diff --git a/janitor/module/clean.py b/janitor/module/clean.py index a1ea786..f46ac54 100644 --- a/janitor/module/clean.py +++ b/janitor/module/clean.py @@ -15,8 +15,8 @@ from fnmatch import fnmatch from os.path import exists -from util import file_mgmt -from history import History +from .util import file_mgmt +from .history import History class Clean(object): @@ -32,7 +32,7 @@ def __init__(self, vms, wlist_path, volumes, driver): def read_whitelist(self): """ """ if not exists(self.wlist_path): - raise Exception("File %s not found" % self.wlist_path) + raise Exception(f"File {self.wlist_path} not found") wlist = file_mgmt('r', file_path=self.wlist_path) return wlist.split() @@ -53,9 +53,10 @@ def run(self): for vm in self.vm_list: if vm['name'] not in to_keep_names: if self.driver.delete_instance(vm): - deleted.append(vm) + print(vm['name']) + # deleted.append(vm) else: - print "Could not delete vm %s" % vm['name'] + print(f"Could not delete vm {vm['name']}") # delete zoombies floating ips ips_deleted = [] @@ -64,7 +65,7 @@ def run(self): if self.driver.delete_floating_ip(ip['id']): ips_deleted.append(ip) else: - print "Could not delete ip %s" % ip + print(f"Could not delete ip {ip}") # delete volumes vols_deleted = [] diff --git a/janitor/module/history.py b/janitor/module/history.py index 322525e..9249b3d 100644 --- a/janitor/module/history.py +++ b/janitor/module/history.py @@ -15,7 +15,7 @@ from prettytable import PrettyTable from datetime import datetime -from util import file_mgmt +from .util import file_mgmt from os import getenv from os.path import join, exists @@ -38,14 +38,14 @@ def print_history(self): """print history file""" if exists(self.history_path): history = file_mgmt('r', file_path=self.history_path) - print history + print(history) else: - print "no history yet" + print("no history yet") def print_report(self): """print current report""" if self.report: - print self.report + print(self.report) def create_report(self): """ @@ -57,17 +57,17 @@ def create_report(self): # instances/vm report vm_report = PrettyTable(['TIMESTAMP', 'ACTION', 'NAME', 'IPs', - 'IMAGE', 'FLAVOR', 'CREATED AT (UTC)']) + 'IMAGE', 'CREATED AT (UTC)']) now = datetime.now().strftime('%Y-%m-%d %H:%M:%S') for vm in self.deleted: vm_report.add_row([now, 'deleted', vm['name'], vm['ips'], vm['image'], - vm['flavor'], vm['created_at']]) + vm['created_at']]) for vm in self.keep: vm_report.add_row([now, 'in whitelist', vm['name'], vm['ips'], vm['image'], - vm['flavor'], vm['created_at']]) + vm['created_at']]) # floating ip report ip_report = PrettyTable(['TIMESTAMP', 'ACTION', 'FLOATING IP']) @@ -86,13 +86,13 @@ def create_report(self): # group all reports full_report = None - if vm_report._rows.__len__() > 0: + if vm_report.rows.__len__() > 0: full_report = str(vm_report) + "\n" - if ip_report._rows.__len__() > 0: + if ip_report.rows.__len__() > 0: full_report = full_report + str(ip_report) + "\n\n" - if vol_report._rows.__len__() > 0: + if vol_report.rows.__len__() > 0: full_report = full_report + str(vol_report) + "\n\n" return full_report diff --git a/janitor/provider/openstack.py b/janitor/provider/openstack.py index 85ae1cf..7c4fd46 100644 --- a/janitor/provider/openstack.py +++ b/janitor/provider/openstack.py @@ -144,7 +144,7 @@ def get_all_instances(self): os_img = self.glance.images.get(vm.image['id']) image = os_img['name'] except Exception as ex: - print ex + print(ex) image = "NA, prev deleted" if vm.name and vm.name is not None: @@ -170,9 +170,9 @@ def get_all_instances(self): vm_list.append(instance) except Exception as ex: # for troubleshooting - print ex + print(ex) if vm: - print "WARN: check this vm %s" % vm + print(f"WARN: check this vm {vm}") raise (ex) return vm_list @@ -182,14 +182,15 @@ def delete_instance(self, vm): self.nova.servers.delete(vm['obj']) return True except Exception as ex: - print ex + print(ex) def get_server_ips(self, server): """get IPs for a given server object""" server_ips = [] - for key, value in server.addresses.iteritems(): - for elem in value: - server_ips.append(elem['addr']) + if server.addresses: + for key, value in server.addresses.items(): + for elem in value: + server_ips.append(elem['addr']) return server_ips def get_zoombies_floating_ips(self): @@ -207,11 +208,11 @@ def delete_floating_ip(self, ip): self.neutron.delete_floatingip(ip) return True except Exception as ex: - print ex + print(ex) def get_volume(self, status): """query cinder for all available volume""" try: return self.cinder.volumes.list(search_opts=status) except Exception as ex: - print ex + print(ex) diff --git a/requirements/devel.txt b/requirements/devel.txt index 6394997..80b2c46 100644 --- a/requirements/devel.txt +++ b/requirements/devel.txt @@ -1,10 +1,12 @@ -r production.txt -flake8==2.6.2 +flake8 coverage>=3.7.1 mock>=1.0.1 setuptools -# docs +# docs and static code analysis Sphinx pep8 +mccabe>=0.6,<0.7 # version needed by pylint pylint -pytest \ No newline at end of file +pytest +nose \ No newline at end of file diff --git a/requirements/production.txt b/requirements/production.txt index 23e2f29..c0903fc 100644 --- a/requirements/production.txt +++ b/requirements/production.txt @@ -1,5 +1,5 @@ click -PrettyTable +PrettyTable<0.8,>=0.7.1 # version required by python-glanceclient==3.3.0 python-openstackclient python-neutronclient python-novaclient diff --git a/tests/README.md b/tests/README.md index d986e4e..f758bcb 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,45 +1,53 @@ -RUNNING TESTS -============= +# Janitor Tests -Openstack tests requires you have your openstack tenant openrc.sh file that -provides the Openstack credentials. Janitor's tests reads system -environment variable if you perform a source to your openrc.sh or from a -file that should be placed at janitor/tests/test-openrc.sh. There is a -test-openrc-sample.sh file you can use to start it, just remove -sample or copy -and paste to a new file without -sample and fill out with your credentials. +**Requirements:** +* Openstack cluster properly setup +* Openstack tenant and credentials +* Openstack openrc.sh file +* whitelist.txt file +* python venv with janitor/requirements installed see [local dev environment](../README.md#local-dev-environment) -Also whitelist.txt file is required and should be placed at same place like: -janitor/tests/whitelist.txt +The tests can handle authentication by environment variables or loading the variables from a physical openrc.sh file, +you can use the template `janitor/tests/test-openrc-sample.sh` creating a copy and replacing the content with your +account and Openstack data. -DATA FOR TESTS ---------------- +Or using environment variable to point the location of your openrc.sh file as showing below: +``` +export OPENSTACK_CREDS=/home/user/openrc.sh +``` -running the openstack nova cli below you can generate content or vms for your -tests: +`whitelist.txt` file is required and should be placed in the same folder: `janitor/tests/whitelist.txt` which will +contain the name of virtual machines to be kept and not deleted when Janitor runs. - nova boot --flavor 2 --key-name mykeyname --image e636fa2e-a1fc-48c0-97c4-04fc74651281 test2 +## Generating data for test +It is an optional step, feel free to skip it if you have data in your Openstack for testing. -HOW TO RUN ALL TESTS --------------------- +Running the commands below you can generate content or vms for your tests, replace values from `--key-name --image --network` +with correspondent from your Openstack: -you have to install janitor from branch or version you want to run the tests. -Go to janitor folder and run: +``` +openstack server create --key-name MyKeyName --flavor m1.small --image FEDORA-34-x86_64-latest --network d655dcd0-b593-439c-997b-aa5bc8c03a3a Fedora34 +openstack server create --key-name MyKeyName --flavor m1.small --image FEDORA-33-x86_64-latest --network d655dcd0-b593-439c-997b-aa5bc8c03a3a Fedora33 +openstack server create --key-name MyKeyName --flavor m1.small --image FEDORA-32-x86_64-latest --network d655dcd0-b593-439c-997b-aa5bc8c03a3a Fedora32 +``` + +## Running tests - python -m pytest tests -v +``` +cd tests +# running all tests from a file +pytest test_openstack_credentials.py -v -HOW TO RUN AN INDIVIDUAL TEST ------------------------------ +# running specific test +pytest test_openstack_instances.py -v -k test_get_ip_all_instances -you have to install janitor from branch or version you want to run the tests. -Go to janitor folder and run: - - python -m pytest tests/openstack/test_instances.py -v - - -ISSUES ------- +# running all tests +pytest -v +``` + +## Issues 1. classpath @@ -49,9 +57,3 @@ ISSUES PYTHONPATH=janitor pytest tests -v on this example I am running the command above from my janitor folder. - - -REFERENCE LINKS ----------------- - -http://www.tilcode.com/pytest-cli-tips-tricks-settings/ diff --git a/tests/helper.py b/tests/helper.py index 5932fc4..080bbd4 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -16,32 +16,31 @@ from os import getenv, getcwd from os.path import exists, join, dirname - ROOT = dirname(getcwd()) def get_openstack_creds(): - ops_creds = getenv("OPENSTACK_CREDS") if ops_creds: - print "Using %s" % ops_creds + print(f"Using {ops_creds}") return ops_creds # root and workspace folder elif exists(join(ROOT, 'test-openrc.sh')): ops_creds = join(ROOT, 'test-openrc.sh') - print "Using %s" % ops_creds + print(f"Using {ops_creds}") return ops_creds # tests folder elif exists(join(ROOT, 'tests/test-openrc.sh')): ops_creds = join(ROOT, 'tests/test-openrc.sh') - print "Using %s" % ops_creds + print(f"Using {ops_creds}") return ops_creds # janitor folder elif exists(join(ROOT, 'janitor/tests/test-openrc.sh')): ops_creds = join(ROOT, 'janitor/tests/test-openrc.sh') - print "Using %s" % ops_creds + print(f"Using {ops_creds}") return ops_creds else: raise Exception("missing Openstack credentials") + OPENSTACK_CREDS_PATH = get_openstack_creds() diff --git a/tests/test_openstack_credentials.py b/tests/test_openstack_credentials.py index d8c23ae..d4e0fc1 100644 --- a/tests/test_openstack_credentials.py +++ b/tests/test_openstack_credentials.py @@ -14,8 +14,8 @@ # import pytest -from provider.openstack import OpenstackSDK -from helper import OPENSTACK_CREDS_PATH +from janitor.provider.openstack import OpenstackSDK +from .helper import OPENSTACK_CREDS_PATH class TestCredentials(object): diff --git a/tests/test_openstack_instances.py b/tests/test_openstack_instances.py index abaecac..c38876f 100644 --- a/tests/test_openstack_instances.py +++ b/tests/test_openstack_instances.py @@ -14,7 +14,7 @@ # import pytest -from provider.openstack import OpenstackSDK +from janitor.provider.openstack import OpenstackSDK from helper import OPENSTACK_CREDS_PATH @@ -30,17 +30,24 @@ def test_get_all_instances(self): def test_get_ip_all_instances(self): openstack = OpenstackSDK(OPENSTACK_CREDS_PATH) servers = openstack.get_all_instances() + vm_no_ip = [] for server in servers: - if openstack.get_server_ips(server['obj']): - assert True - else: + try: + if openstack.get_server_ips(server['obj']): + pass + else: + vm_no_ip.append(server['obj']) + except Exception: assert False + print(f"Found {len(vm_no_ip)} vms with no assigned IP out of {len(servers)}") + assert True + def test_get_zoombies_floating_ips(self): openstack = OpenstackSDK(OPENSTACK_CREDS_PATH) ips_zoombies = openstack.get_zoombies_floating_ips() if ips_zoombies: assert True else: - print "ZERO, it is fine!" + print("ZERO, it is fine!") assert True diff --git a/tests/test_openstacksdk.py b/tests/test_openstacksdk.py index 5fcf73a..0f41e09 100644 --- a/tests/test_openstacksdk.py +++ b/tests/test_openstacksdk.py @@ -15,7 +15,7 @@ import pytest -from provider.openstack import OpenstackSDK +from janitor.provider.openstack import OpenstackSDK from helper import OPENSTACK_CREDS_PATH diff --git a/tests/whitelist.txt b/tests/whitelist.txt index 48ab2c3..9553ccc 100644 --- a/tests/whitelist.txt +++ b/tests/whitelist.txt @@ -1,4 +1,24 @@ centos rhel jslave* -windows* \ No newline at end of file +windows* +*jeff* +*ecerquei* +*esakaiev* +*pakotvan* +*dlezzoum* +*jenkins* +jslave* +*insights* +*testday* +*fstavela* +*msager* +*opacut* +psav* +jaudet* +*vbelchio* +pvala* +*mlahane* +*uenkhbay* +*dgaikwad* +*digitronik* \ No newline at end of file From 4c55b194d61d6c4c145450ae998438a939fada86 Mon Sep 17 00:00:00 2001 From: ecerquei Date: Fri, 30 Apr 2021 11:13:24 -0400 Subject: [PATCH 3/5] updated doc and RPM * updated documentation and README * updated Makefile, Make spec, setup to be in accordance with python3 upgrade --- Makefile | 2 +- README.md | 180 ++++++++++++++-------------- docs/source/examples.rst | 3 + docs/source/motivation.rst | 16 +-- janitor.1 | 17 +-- janitor.spec.in | 6 +- janitor/cli.py | 2 +- setup.py | 1 + tests/test_openstack_credentials.py | 2 +- tox.ini | 2 +- 10 files changed, 120 insertions(+), 111 deletions(-) diff --git a/Makefile b/Makefile index b270942..09ba3c1 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ MAN=janitor.1 VERSION=0.2 RPMDIST=$(shell rpm --eval '%dist') #RELEASE=1$(rpmsuffix)$(RPMDIST) -RELEASE=1 +RELEASE=0 PWD=$(shell bash -c "pwd -P") RPMTOP=$(PWD)/rpmbuild SPEC=$(NAME).spec diff --git a/README.md b/README.md index eb89624..80066fe 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,8 @@ It `is not` an official Openstack's tool, and Janitor's code is wrapping Opensta * [Setup your dev environment](README.md#local-dev-environment) * [Running janitor functional tests](tests/README.md#janitor-tests) * [Contributing](README.md#contributing) - +* [Build RPM and release](README.md#build-rpm-and-release) +* [DEMO](README.md#demo) ## Motivation @@ -37,28 +38,62 @@ tenant, all managed by API and remote calls.` ## Install and Usage +For [Fedora](https://fedoraproject.org/): + +1. from [Copr](https://copr.fedorainfracloud.org/coprs/eduardocerqueira/janitor/) + ``` -python janitor/cli.py openstack --openrc /root/insights-qa-openrc.sh --whitelist /var/lib/jenkins/workspace/janitor/janitor_whitelist.txt --keystone v3 -python janitor/cli.py openstack --openrc /home/ecerquei/osp/insights-qa-openrc.sh --whitelist /janitor/janitor_whitelist.txt --keystone v3 -openstack --openrc /home/ecerquei/osp/janitor-openrc.sh --whitelist /home/ecerquei/osp/janitor-whitelist.txt --keystone v3 +dnf copr enable eduardocerqueira/janitor +dnf install janitor ``` -### usage +or if you prefer you can install the repo from https://copr.fedorainfracloud.org/coprs/eduardocerqueira/janitor/ +and maybe is needed to disable **gpgcheck=0** -clean up virtual machines and release floating ips for Openstack keep items declared in the whitelist.txt file: +``` +[eduardocerqueira-janitor] +name=Copr repo for janitor owned by eduardocerqueira +baseurl=https://copr-be.cloud.fedoraproject.org/results/eduardocerqueira/janitor/epel-7-$basearch/ +type=rpm-md +skip_if_unavailable=True +gpgcheck=0 +gpgkey=https://copr-be.cloud.fedoraproject.org/results/eduardocerqueira/janitor/pubkey.gpg +repo_gpgcheck=0 +enabled=1 +enabled_metadata=1 +``` -1. load your Openstack rc to system environment variable or pass as parameter -2. run janitor +2. from local build:: - source ~/mytenant-openrc.sh +``` +make rpm +dnf install rpmbuild/RPMS/x86_64/janitor-0.2-0.x86_64.rpm +``` - janitor openstack --whitelist /tmp/whitelist.txt --keystone v3 - janitor openstack --openrc /tmp/mytenant-openrc.sh --whitelist /tmp/whitelist.txt --keystone v3 +for CentOS: -listing history for your janitor: +**requires openstack repo**. check the latest repo https://wiki.openstack.org/wiki/Release_Naming + +``` +sudo yum install centos-release-openstack-newton.noarch +sudo yum install install rpmbuild/RPMS/x86_64/janitor-0.2-0.x86_64.rpm +``` + +using Janitor to clean up left-over virtual machines and release floating ips for Openstack tenant: + +``` +source ~/mytenant-openrc.sh +janitor openstack --whitelist /tmp/whitelist.txt --keystone v3 - janitor history +# or passing the path for your openrc file +janitor openstack --openrc /tmp/mytenant-openrc.sh --whitelist /tmp/whitelist.txt --keystone v3 +``` + +listing history for your janitor: +``` +janitor history +``` ## local dev environment @@ -68,8 +103,8 @@ listing history for your janitor: * packages: ``` -# install packages needed for build and release RPM, and generate doc -sudo dnf install redhat-rpm-config python3-devel gcc python3-devel python3-pip python3-wheel python3-setuptools, python3-sphinx +# install packages needed to interact with make, build RPM, and generate doc +sudo dnf install redhat-rpm-config rpm-build python3-devel gcc python3-devel python3-pip python3-wheel python3-setuptools, python3-sphinx # prep your python env git clone git@github.com:eduardocerqueira/janitor.git @@ -80,14 +115,27 @@ pip install -r requirements/devel.txt # check janitor installation python janitor/cli.py --help +openstack --openrc /home/ecerquei/osp/janitor-openrc.sh --whitelist /home/ecerquei/osp/janitor-whitelist.txt --keystone v3 ``` see [running janitor functional tests](tests/README.md#janitor-tests) +also you can explore the **make tasks** running `make`. + +when running `make doc` the generated doc can be access at : file:///home/user/git/janitor/docs/build/html/index.html -or using **make** it requires basic packages in your machine I recommend: python-setuptools, python-sphinx, python3-devel and gcc -## RPM / Build +## Contributing + +Any idea, suggestions and pacthes are welcome to this project! Fork the project, make your code change, run the test locally +to ensure your changes are not breaking any functionality, remember to run the code static analysis before submitting your +PR and if needed open [issues or discussion](https://github.com/eduardocerqueira/janitor/issues) on this project. + +``` +# make flake8 +``` + +## Build RPM and release $ make @@ -107,19 +155,39 @@ Running from your local machine, you can generate your own RPM running: $ make rpm -and if your environment is setup properly you should have your RPM at: /home/user/git/janitor/rpmbuild/RPMS/x86_64/janitor-0.0.1-1.x86_64.rpm +if your environment is properly setup you should have your RPM at: /home/user/git/janitor/rpmbuild/RPMS/x86_64/janitor-0.0.2-0.x86_64.rpm -janitor is being built on Fedora Copr: https://copr.fedorainfracloud.org/coprs/eduardocerqueira/janitor/builds/ +janitor is being built on [Fedora Copr https://copr.fedorainfracloud.org/coprs/eduardocerqueira/janitor/builds/](https://copr.fedorainfracloud.org/coprs/eduardocerqueira/janitor/builds/) running a new build you need to check your ~/.config/copr-fedora file and run: make build +Before starting the release process, check your account permissions in Copr. + + $ make srpm + + 1. copy rpmbuild/SRPMS/janitor-0.0.2-0.src.rpm to janitor/copr + 2. push janitor/copr to github + + `copr-cli` will be used, installed by `sudo yum/dnf install copr-cli` and configure it. + +Request as `Builder` for projects `janitor`, wait until admin approves. + +$ copr-cli build janitor https://github.com/eduardocerqueira/janitor/raw/master/copr/janitor-0.0.2-0.src.rpm +Go and grab a cup of tea or coffee, the release build will be come out soon :: + # tag based builds: `https://copr.fedorainfracloud.org/coprs/eduardocerqueira/janitor/builds/` -DEMO ----- +**Copr and RPM release helpful Links** +* https://fedorahosted.org/copr/wiki/HowToEnableRepo +* http://fedoraproject.org/wiki/Infrastructure/fedorapeople.org#Accessing_Your_fedorapeople.org_Space +* https://fedorahosted.org/copr/wiki/UserDocs#CanIgiveaccesstomyrepotomyteammate +* https://copr.fedoraproject.org/api/ + + +## DEMO Running the program without parameters: @@ -139,7 +207,7 @@ Running the program without parameters: Running the program with with parameters to make clean-up: - [ecerquei@dev ~]$ janitor openstack --openrc /home/ecerquei/git/janitor/tests/test-openrc.sh --whitelist /home/ecerquei/git/janitor/tests/whitelist.txt --keystone v2 + [ecerquei@dev ~]$ janitor openstack --openrc /home/ecerquei/git/janitor/tests/test-openrc.sh --whitelist /home/ecerquei/git/janitor/tests/whitelist.txt --keystone v3 +---------------------+--------------+------------------+-----------------------------+--------------------------------------------+--------+----------------------+ | TIMESTAMP | ACTION | NAME | IPs | IMAGE | FLAVOR | CREATED AT (UTC) | +---------------------+--------------+------------------+-----------------------------+--------------------------------------------+--------+----------------------+ @@ -189,54 +257,8 @@ Running the program with parameter to print history file: | 2017-07-13 15:00:57 | deleted | 358be8d1-6d4a-4db7-973f-8369d4ff86f7 | +---------------------+---------+--------------------------------------+ -## Contributing - - - -## install - -Installing from your local machine, after you build your own RPM just run: - -for Fedora: - - sudo dnf install /home/user/git/janitor/rpmbuild/RPMS/x86_64/janitor-0.0.1-1.x86_64.rpm - -for CentOS: - - *requires openstack repo*. you can consult here the latest repo https://wiki.openstack.org/wiki/Release_Naming - - sudo yum install centos-release-openstack-newton.noarch - sudo yum install /home/user/git/janitor/rpmbuild/RPMS/x86_64/janitor-0.0.1-1.x86_64.rpm - -To install from latest RPM: - -**repo:** https://copr.fedorainfracloud.org/coprs/eduardocerqueira/janitor/ - -for Fedora: - - sudo dnf copr enable eduardocerqueira/janitor - -or if needed get the repo from https://copr.fedorainfracloud.org/coprs/eduardocerqueira/janitor/ -and maybe is needed to disable **gpgcheck=0** - - - [eduardocerqueira-janitor] - name=Copr repo for janitor owned by eduardocerqueira - baseurl=https://copr-be.cloud.fedoraproject.org/results/eduardocerqueira/janitor/epel-7-$basearch/ - type=rpm-md - skip_if_unavailable=True - gpgcheck=0 - gpgkey=https://copr-be.cloud.fedoraproject.org/results/eduardocerqueira/janitor/pubkey.gpg - repo_gpgcheck=0 - enabled=1 - enabled_metadata=1 - - -or if links above don't work go to Copr Janitor project for more details how to proceed from here. - -INSTALLATION FAQ ----------------- +### issues Running on CentOS7 even having EPEL and centos-release-openstack-newton I got this error in one of my CentOS server. @@ -293,23 +315,3 @@ the latest version I got was 2.11 that still don't satisfacts. The trick here was, forcing reinstall requests using pip: pip install requests --upgrade - - -## MORE INFO - -For others topics listed below, please generate the sphinx doc in your local machine running the command: - - $ make doc - -and from a browser access: file:///home/user/git/janitor/docs/build/html/index.html - -* Install: -* Guide: -* Build: -* Development: - - - ## How to contribute - - Feel free to fork and send me pacthes or messages if you think this tool can be helpful for any other scenario. - diff --git a/docs/source/examples.rst b/docs/source/examples.rst index 1c5ee3b..706ca84 100644 --- a/docs/source/examples.rst +++ b/docs/source/examples.rst @@ -14,6 +14,9 @@ clean up virtual machines and release floating ips for Openstack keep items decl source ~/mytenant-openrc.sh janitor --openstack --whitelist /tmp/whitelist.txt +or passing openrc as argument: + + janitor openstack --openrc /root/openrc.sh --whitelist /var/lib/jenkins/workspace/janitor/janitor_whitelist.txt --keystone v3 listing history for your janitor: diff --git a/docs/source/motivation.rst b/docs/source/motivation.rst index 3e1cc0a..36c1df3 100644 --- a/docs/source/motivation.rst +++ b/docs/source/motivation.rst @@ -4,10 +4,12 @@ Motivation ========== -One of my Openstack tenants that I use to run tests for an internal tool has -a very limited quota so all the time I need to stop my tests and automations -and spend few minutes cleaning up public/floating ips, destroying -virtual machines and others. The idea to have janitor or a butler is actually -an old idea and a coworker had implemented it but in a more sophisticated -and fashion way this is a simply version and currently only focusing in -Openstack maybe further I'll extend it to AWS, Openshift and others. +I have been using OpenStack continuously since 2014 as a resource to run workloads with the main purpose of +testing software. Manage a healthy Openstack tenant quota is vital when working with CI/CD and a large number of +automated pipelines which run 24/7. A long time ago I and my team were having a lot of headaches with one of internal +Openstack the cluster which at that time had a very limited quota and workload capacity but was important for the +test workflow. With the constant false-positive for test failing due to lack of public/floating IP, storage/volume, +and even virtual CPU when provisioning new VMs, we got the idea of implementing Janitor, a super simple script/codes +to `automate the clean-up task of deleting left-over virtual machines as well network and volume from an Openstack +tenant, all managed by API and remote calls.` + diff --git a/janitor.1 b/janitor.1 index c520428..4a12673 100644 --- a/janitor.1 +++ b/janitor.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "JANITOR" "1" "Jul 13, 2017" "0.0.1" "janitor" +.TH "JANITOR" "1" "Apr 30, 2021" "0.0.1" "janitor" .SH NAME janitor \- janitor Documentation . @@ -34,13 +34,14 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] Contents: .SH MOTIVATION .sp -One of my Openstack tenants that I use to run tests for an internal tool has -a very limited quota so all the time I need to stop my tests and automations -and spend few minutes cleaning up public/floating ips, destroying -virtual machines and others. The idea to have janitor or a butler is actually -an old idea and a coworker had implemented it but in a more sophisticated -and fashion way this is a simply version and currently only focusing in -Openstack maybe further I’ll extend it to AWS, Openshift and others. +I have been using OpenStack continuously since 2014 as a resource to run workloads with the main purpose of +testing software. Manage a healthy Openstack tenant quota is vital when working with CI/CD and a large number of +automated pipelines which run 24/7. A long time ago I and my team were having a lot of headaches with one of internal +Openstack the cluster which at that time had a very limited quota and workload capacity but was important for the +test workflow. With the constant false\-positive for test failing due to lack of public/floating IP, storage/volume, +and even virtual CPU when provisioning new VMs, we got the idea of implementing Janitor, a super simple script/codes +to \fIautomate the clean\-up task of deleting left\-over virtual machines as well network and volume from an Openstack +tenant, all managed by API and remote calls.\fP .SH EXAMPLES .sp USAGE: diff --git a/janitor.spec.in b/janitor.spec.in index 36be160..1814ebc 100644 --- a/janitor.spec.in +++ b/janitor.spec.in @@ -25,16 +25,16 @@ janitor is a Linux helper tool to perform clean-up tasks to Openstack based on w %setup -q -n %{name} %build -%{__python} setup.py build +%{__python3} setup.py build %install -%{__python} setup.py install -O1 --skip-build --root %{buildroot} +%{__python3} setup.py install -O1 --skip-build --root %{buildroot} mkdir -p %{buildroot}/%{_mandir}/man1 cp janitor.1 %{buildroot}/%{_mandir}/man1/janitor.1 %files %defattr(755,root,root,755) -%{python_sitelib}/janitor* +%{python3_sitelib}/janitor* %attr (755,root,root)/usr/bin/janitor %doc README.md %doc AUTHORS diff --git a/janitor/cli.py b/janitor/cli.py index 780726b..e8e75f4 100644 --- a/janitor/cli.py +++ b/janitor/cli.py @@ -47,7 +47,7 @@ def openstack(openrc, whitelist, keystone): exit(1) # clean up clean = Clean(vms, whitelist, volumes, openstack) - #clean.run() + clean.run() @click.command(short_help='show janitor history') diff --git a/setup.py b/setup.py index 4d9b6ae..b21897c 100644 --- a/setup.py +++ b/setup.py @@ -44,6 +44,7 @@ def get_install_requires(): requires.append(line) return requires, links + install_requires, dependency_links = get_install_requires() setup( diff --git a/tests/test_openstack_credentials.py b/tests/test_openstack_credentials.py index d4e0fc1..65daf41 100644 --- a/tests/test_openstack_credentials.py +++ b/tests/test_openstack_credentials.py @@ -23,7 +23,7 @@ class TestCredentials(object): def test_negative_credentials(self): try: OpenstackSDK() - except: + except Exception: assert True def test_credentials(self): diff --git a/tox.ini b/tox.ini index 29196b8..a88ab5c 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,7 @@ skipsdist = True deps = -r{toxinidir}/requirements/devel.txt setenv = PYTHONPATH = {toxinidir}:{toxinidir} -commands = python driver.py --help +commands = python cli.py --help [run] branch = True From ffd21f86b9ff594bae39503b0390e59213e30cda Mon Sep 17 00:00:00 2001 From: ecerquei Date: Fri, 30 Apr 2021 11:18:47 -0400 Subject: [PATCH 4/5] added copr-cli --- README.md | 4 ++-- janitor.1 | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 80066fe..0a4fce0 100644 --- a/README.md +++ b/README.md @@ -103,8 +103,8 @@ janitor history * packages: ``` -# install packages needed to interact with make, build RPM, and generate doc -sudo dnf install redhat-rpm-config rpm-build python3-devel gcc python3-devel python3-pip python3-wheel python3-setuptools, python3-sphinx +# install packages needed to interact with make, build RPM, make release to Copr, and generate doc +sudo dnf install redhat-rpm-config rpm-build python3-devel gcc python3-devel python3-pip python3-wheel python3-setuptools, python3-sphinx copr-cli # prep your python env git clone git@github.com:eduardocerqueira/janitor.git diff --git a/janitor.1 b/janitor.1 index 4a12673..cd2a885 100644 --- a/janitor.1 +++ b/janitor.1 @@ -63,6 +63,13 @@ janitor –openstack –whitelist /tmp/whitelist.txt .UNINDENT .UNINDENT .sp +or passing openrc as argument: +.INDENT 0.0 +.INDENT 3.5 +janitor openstack –openrc /root/openrc.sh –whitelist /var/lib/jenkins/workspace/janitor/janitor_whitelist.txt –keystone v3 +.UNINDENT +.UNINDENT +.sp listing history for your janitor: .INDENT 0.0 .INDENT 3.5 From 898867f4d31ee7924f4399aa3594d7a871a5b2d5 Mon Sep 17 00:00:00 2001 From: ecerquei Date: Fri, 30 Apr 2021 11:34:11 -0400 Subject: [PATCH 5/5] adding deletion code in try/catch to prevent interruption during execution --- janitor/module/clean.py | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/janitor/module/clean.py b/janitor/module/clean.py index f46ac54..87668e6 100644 --- a/janitor/module/clean.py +++ b/janitor/module/clean.py @@ -52,28 +52,36 @@ def run(self): deleted = [] for vm in self.vm_list: if vm['name'] not in to_keep_names: - if self.driver.delete_instance(vm): - print(vm['name']) - # deleted.append(vm) - else: - print(f"Could not delete vm {vm['name']}") + try: + if self.driver.delete_instance(vm): + deleted.append(vm) + else: + print(f"Could not delete vm {vm['name']}") + except Exception as ex: + print(ex) # delete zoombies floating ips ips_deleted = [] - zoombies_ips = self.driver.get_zoombies_floating_ips() - for ip in zoombies_ips: - if self.driver.delete_floating_ip(ip['id']): - ips_deleted.append(ip) - else: - print(f"Could not delete ip {ip}") + zombie_ips = self.driver.get_zoombies_floating_ips() + try: + for ip in zombie_ips: + if self.driver.delete_floating_ip(ip['id']): + ips_deleted.append(ip) + else: + print(f"Could not delete ip {ip}") + except Exception as ex: + print(ex) # delete volumes vols_deleted = [] for vol in self.volumes: # extra verification for not attached volumes - if len(vol.attachments) == 0: - self.driver.cinder.volumes.delete(vol.id) - vols_deleted.append(vol.id) + try: + if len(vol.attachments) == 0: + self.driver.cinder.volumes.delete(vol.id) + vols_deleted.append(vol.id) + except Exception as ex: + print(ex) # history history = History(to_keep, deleted, ips_deleted, vols_deleted)