sample fabric (fabfile) to share common logic and allow multiple projects/environments - python

i am new to python and fabric. we currently use capistrano and have a setup similar to this:
/api-b2b
- Capfile (with generic deployment/setup info)
/int - target host config (like ip, access etc.)
/prod - target host config (like ip, access etc.)
/dev - target host config (like ip, access etc.)
/api-b2c
/int
/prod
/dev
/application1
/int
/prod
/dev
/application2
/int
/prod
/dev
we are not happy with capistrano for handling our java apps - fabric looks like a better (simpler) alternative.
all the example fabfiles i have seen so far are "relatively simple" in that they only handle one application for different hosts. i'd like to see some code where different app/hosts are handled by the same fabric files/infrastructure (like inheritance etc.) to share the same logic for common tasks like git handling, directory creation, symlinks etc.. i hope you get what i mean. i want the whole logic to be the same, just the apps config is different (git repo, target directory). all the rest is the same accross the apps (same server layout...)
i want to be able to enter something like this
$ cd api-b2b
$ fab env_prod deploy
$ cd api-b2c
$ fab env_prod deploy
or
$ fab env_prod deploy:app=api=b2b
$ fab env_prod deploy:app=api=b2c
any help (and pointers to sample files) highly appreciated
cheers
marcel

If you genuinely want reuse amongst your fabric code, the most robust approach is to refactor the commonalities out and make it a python module. There are modules like fabtools and cusine that are good examples of what it is possible to do.
If you're looking to have multiple projects, there are a few ways to achieve that result. Assuming you're using a fabfile directory (rather than a single fabfile.py), you'll have a structure like this.
/fabfile
__init__.py
b2b.py
b2c.py
Assuming that you have:
# b2b.py / b2c.py
from fabric.api import *
#task
def deploy():
# b2b/b2c logic
pass
When you run fab -l (with an empty __init__.py) you'll see:
Available commands:
b2b.deploy
b2c.deploy
To get closer to what you're looking for, you can dynamically lookup, from an argument, which deployment target you want to run:
# __init__.py
from fabric.api import *
import b2b
import b2c
#task
def deploy(api):
globals()[api].deploy()
Which means that on the command line, I can run fab deploy:api=b2b or fab deploy:api=b2c.
Edit 27th Jan
Specifying one or machines for a task to run on can be achieved on the command line with the -H or -R switches, using #task or #role decorators, or the settings in the fabric environment (env.hosts and env.roles). The fabric documentation has extensive examples on the execution model that shows you all the details.
One way to do this (and potentially not the best way depending on your application) is to dynamically alter the host lists based on the api and the target environment.
# __init__.py
from fabric.api import *
import b2b
import b2c
#task
def deploy(api, target='test'):
func = globals()[api].deploy
hosts = globals()[api].deploy_hosts(target)
execute(func, hosts=hosts)
And now the b2b.py and b2c.py files will look something like:
# b2b.py / b2c.py
#task
def deploy():
# b2b/b2c logic
pass
def deploy_hosts(target):
return {
'test' : ['localhost'],
'prod' : ['localhost'],
'int' : ['localhost'],
}[target]

Related

Simple way to delete existing pods from Python

I have end-to-end tests written in pytest, running on a Kubernetes cluster in Namespace foo. Now I want to add simple chaos engineering to the tests to check the resilience of my services. For this, I only need to delete specific pods within foo -- since K8s restarts the corresponding service, this simulates the service being temporarily unreachable.
What is a simple way to delete a specific pod in the current namespace with Python?
What I have tried so far:
Since I did not find a suitable example in https://github.com/kubernetes-client/python/tree/master/examples, but the ones using pods looked quite complex,
I looked into kubetest, which seems very simple and elegant.
I wanted to use the kube fixture and then do something like this:
pods = kube.get_pods()
for pod in pods:
if can_be_temporarily_unreachable(pod):
kube.delete(pod)
I thought calling pytest with parameter --in-cluster would tell kubetest to use the current cluster setup and not create new K8s resources.
However, kubetest wants to create a new namespace for each test case that uses the kube fixture, which I do not want.
Is there a way to tell kubetest not to create new namespaces but do everything in the current namespace?
Though kubetest looks very simple and elegant otherwise, I am happy to use another solution, too.
A simple solution that requires little time and maintenance and does not complicate (reading) the tests would be awesome.
you can use delete_namespaced_pod(from CoreV1Api) to delete specific pods within a namespace.
Here is an example:
from kubernetes import client, config
from kubernetes.client.rest import ApiException
config.load_incluster_config() # or config.load_kube_config()
configuration = client.Configuration()
with client.ApiClient(configuration) as api_client:
api_instance = client.CoreV1Api(api_client)
namespace = 'kube-system' # str | see #Max Lobur's answer on how to get this
name = 'kindnet-mpkvf' # str | Pod name, e.g. via api_instance.list_namespaced_pod(namespace)
try:
api_response = api_instance.delete_namespaced_pod(name, namespace)
print(api_response)
except ApiException as e:
print("Exception when calling CoreV1Api->delete_namespaced_pod: %s\n" % e)
A continuation to an existing answer by Tibebes. M
How do I find out the namespace that the code itself is running in?
There's two ways to do this:
Use Downward API to pass pod namespace to a pod environment variable:
https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/#the-downward-api
https://kubernetes.io/docs/tasks/inject-data-application/downward-api-volume-expose-pod-information/#capabilities-of-the-downward-api See metadata.namespace
Use your template engine / helm to additionally pass environment variable namespace to a pod. Example:
env:
- name: CURRENT_NAMESPACE
value: "{{ .Values.namespace }}"

How can I change the logfile naming for the cluster scheduler in snakemake?

I am running job on a PBS TORQUE cluster and want to customize my log scripts for rules repeated for many files.
The default naming scheme is for each script for each rule snakejob.{rulename}.{id}.sh.o26730731, e.g. snakejob.all.7.sh.o26730731, where only the ending varies for different files (as they are executed one after another). This comes from the script snakemake creates for submission to the cluster.
I can specify a common log-directory to qsub using the -e or -o options.
I know that profiles exist or that one could use wildcards, something like (I have to test that):
snakemake --jobs 10 --cluster "qsub -o logs/{wildcards.file} -e logs/{wildcards.file}"
Alternatively the naming of the script temporarily saved by snakemake under .snakemake/tmp<hash> could be altered to achieve unique naming of logs per file.
I tried to set the log-directory in the rule, but this did not work when I specified a directory (missing .log):
rule target:
input:
# mockfile approach: https://stackoverflow.com/a/53751654/9684872
# replace? https://snakemake.readthedocs.io/en/stable/snakefiles/rules.html#directories-as-outputs
file = expand(os.path.join(config['DATADIR'], "{file}", "{file}.txt"), file=FILES)
rule execute:
log:
#dir = os.path.join(config['DATADIR'], "{file}") # Building DAG is stuck in endless loop
dir = os.path.join(config['DATADIR'], "{file}.log") # works
params:
logdir = os.path.join(config['DATADIR'], "{file}") #works
So what is your approach or what would you suggest how this could be solved best to have logs identified by the {file} wildcard?

Python Azure Batch - Permission Denied Linux node

I am running a python script on several Linux nodes (after the creation of a pool) using Azure Batch. Each node uses 14.04.5-LTS version of Ubuntu.
In the script, I am uploading several files on each node and then I run several tasks on each one of these nodes. But, I get a "Permission Denied" error when I try to execute the first task. Actually, the task is an unzip of few files (fyi, the uploading of these zip files went well).
This script was running well until last weeks. I suspect an update of Ubuntu version but maybe it's something else.
Here is the error I get :
error: cannot open zipfile [ /mnt/batch/tasks/shared/01-AXAIS_HPC.zip ]
Permission denied
unzip: cannot find or open /mnt/batch/tasks/shared/01-AXAIS_HPC.zip,
Here is the main part of the code :
credentials = batchauth.SharedKeyCredentials(_BATCH_ACCOUNT_NAME,_BATCH_ACCOUNT_KEY)
batch_client = batch.BatchServiceClient(
credentials,
base_url=_BATCH_ACCOUNT_URL)
create_pool(batch_client,
_POOL_ID,
application_files,
_NODE_OS_DISTRO,
_NODE_OS_VERSION)
helpers.create_job(batch_client, _JOB_ID, _POOL_ID)
add_tasks(batch_client,
_JOB_ID,
input_files,
output_container_name,
output_container_sas_token)
with add_task :
def add_tasks(batch_service_client, job_id, input_files,
output_container_name, output_container_sas_token):
print('Adding {} tasks to job [{}]...'.format(len(input_files), job_id))
tasks = list()
for idx, input_file in enumerate(input_files):
command = ['unzip -q $AZ_BATCH_NODE_SHARED_DIR/01-AXAIS_HPC.zip -d $AZ_BATCH_NODE_SHARED_DIR',
'chmod a+x $AZ_BATCH_NODE_SHARED_DIR/01-AXAIS_HPC/00-EXE/linux/*',
'PATH=$PATH:$AZ_BATCH_NODE_SHARED_DIR/01-AXAIS_HPC/00-EXE/linux',
'unzip -q $AZ_BATCH_TASK_WORKING_DIR/'
'{} -d $AZ_BATCH_TASK_WORKING_DIR/{}'.format(input_file.file_path,idx+1),
'Rscript $AZ_BATCH_NODE_SHARED_DIR/01-AXAIS_HPC/03-MAIN.R $AZ_BATCH_TASK_WORKING_DIR $AZ_BATCH_NODE_SHARED_DIR/01-AXAIS_HPC $AZ_BATCH_TASK_WORKING_DIR/'
'{} {}' .format(idx+1,idx+1),
'python $AZ_BATCH_NODE_SHARED_DIR/01-IMPORT_FILES.py '
'--storageaccount {} --storagecontainer {} --sastoken "{}"'.format(
_STORAGE_ACCOUNT_NAME,
output_container_name,
output_container_sas_token)]
tasks.append(batchmodels.TaskAddParameter(
'Task{}'.format(idx),
helpers.wrap_commands_in_shell('linux', command),
resource_files=[input_file]
)
)
Split = lambda tasks, n=100: [tasks[i:i+n] for i in range(0, len(tasks), n)]
SPtasks = Split(tasks)
for i in range(len(SPtasks)):
batch_service_client.task.add_collection(job_id, SPtasks[i])
Do you have any insights to help me on this issue? Thank you very much.
Robin
looking at the error, i.e.
error: cannot open zipfile [ /mnt/batch/tasks/shared/01-AXAIS_HPC.zip ]
Permission denied unzip: cannot find or open /mnt/batch/tasks/shared/01-AXAIS_HPC.zip,
This seems like that the file is not present at the current shared directory location or it is is not in correct permission. The former is more likely.
Is there any particular reason you are using the shared directory way? also, How are you uploading the file? (i.e. hope that the use of async and await is correctly done, i.e. there is not greedy process which is running your task before the shared_dir stuff is available to the node.)
side note: you own the node so you can RDP / SSH into the node and find it out that the shared_dir are actually present.
Few things to ask will be: how are you uploading these zip files.
Also if I may ask, what is the Design \ user scenario here and how exactly you are intending to use this.
Recommendation:
There are few other ways you can use zip files in the azure node, like via resourcefile or via application package. (The applicaiton package way might suite it better to deal with *.zip file) I have added few documetns and places you can have a look at the sample implementation and guidance for this.
I think a good place to start are: hope material and sample below will help you. :)
Also I would recommend to recreate your pool if it is old which will ensure you have the node running at the latest version.
Azure batch learning path:
Azure batch api basics
Samples & demo link or look here
Detailed walk through depending on what you are using i.e. CloudServiceConfiguration or VirtualMachineConfiguration link.

Odoo - how to use it interactively in python interpreter?

I read here that it might be possible to use python interpreter to access Odoo and test things interactively (https://www.odoo.com/forum/help-1/question/how-to-get-a-python-shell-with-the-odoo-environment-54096), but doing this in terminal:
ipython
import sys
import openerp
sys.argv = ['', '--addons-path=~/my-path/addons', '--xmlrpc-port=8067', '--log-level=debug', '-d test',]
openerp.cli.main()
it starts Odoo server, but I can't write anything in that terminal tab to use it interactively. If for example I write anything like print 'abc', I don't get any output. Am I missing something here?
Sometime I use "logging" library for print output on the console/terminal.
For example:
import logging
logging.info('Here is your message')
logging.warning('Here is your message')
For more details, You may checkout this reference link.
The closest thing I have found to interactive is put the line
import pdb; pdb.set_trace()
in the method I want to inspect, and then trigger that method.
It's clunky, but it works.
As an example, I was just enhancing the OpenChatter implementation for our copy of OpenERP, and during the "figure things out" stage I had that line in .../addons/mail/mail_thread.py::mail_thread.post_message so I could get a better idea of what was happening in that method.
The correct way to do this is with shell:
./odoo-bin shell -d <yourdatabase>
Please, be aware that if you already have an instance of odoo, the port will be busy. In that case, the instance you are opening should be using a different port. So the command should be something like this:
./odoo-bin shell --xmlrpc-port=8888 -d <yourdatabase>
But if you want to have your addons available in the new instance, yo can make something similar to the following:
./odoo-bin shell -c ~/odooshell.conf -d <yourdatabase>
This way you can have in your odooshell.conf whatever you need to have configured (port, addons_path, etc). This way you can work smoothly with your shell.
As I always use docker, this is what I do to have my shell configured in docker:
docker exec -ti <mycontainer> odoo shell -c /etc/odoo/odooshell.conf -d <mydatabase>
You will have the env available to do anything. You can create express python code to make whatever you need. The syntax is very similar to server actions. For example:
partner_ids = env['res.partner'].search([])
for partner in partner_ids:
partner['name'] = partner.name + '.'
env.cr.commit()
Remember to env.cr.commit() if you make any data change.

Python Fabric: Trying to distribute different files to hosts

Is there a way to specify what file will be put to host when using Fabric?
I have a list of hosts with platform specified, such as:
host_list = [['1.1.1.1', 'centos5_x64'], ['2.2.2.2','centos6_x86']]
I want to write something like:
from fabric.api import env, execute
env.hosts = [x[0] for x in hosts_list]
def copy()
put('some_rpm' + <platform>)
execute(copy)
So how can I specify the platform string for each host in env.hosts?
All other steps in my Fabric-based install & test script are equal for each host,
so the obvious answer is to write a 'threaded_copy()' that will do the job. But still, a Fabric-based solution should be much clearer...
As always, I've found the answer myself a while after posting the question here :)
def copy()
platform_string = get_platform_for_host(env.host)
put('some_rpm' + platform_string)
The env.host variable will hold the current host the function is executing on (tested... works).

Categories

Resources