Archivo categoría Programación

Usando AWS Lambda para copiar snapshots de RDS entre regiones

AWS Lambda

En el trabajo surgió la necesidad de hacer respaldos de una base de datos MySQL en RDS entre regiones, pero sin tener una instancia corriendo en la región de destino, es decir, no se quería read replicas. Lo que primero que sugirieron fue usar algún tipo de cron que copiara los respaldos entre regiones. Como seguramente esto ya se había hecho decidí investigar un poco y me conseguí con este excelente artículo que explica cómo hacer la copia usando una función Lambda en Python: Copying RDS snapshot to another region for cross-region recovery

Esta función busca el último snapshot de todas las instancias RDS en la región origen y los copia a la región destino. Por último, la función borra los snapshots anteriores en la región destino para así ahorrar espacio.

La función se puede disparar a través de CloudWacth o incluso eventos de RDS, como por ejemplo cuando se termina de hacer un respaldo en la base de datos.

Paulina Budzon, la autora del artículo, comenta que la función podía mejorase, por lo que aproveché de hacerle algunos ajustes:

  • Added database list to be backup-ed, instead of all databases in RDS
  • Changed variable naming to avoid reference to the destination region
  • Removed source region example reference in SourceDBSnapshotIdentifier string
  • Added variables for source and destination regions

 

Coloco el código acá, pero también puede verse en el fork del proyecto que hice https://github.com/lgallard/aws-maintenance, o directamente en el de Paulina https://github.com/pbudzon/aws-maintenance, el cual ya tiene el merge del pull request:

 

Espero que le sea de utilidad a alguien:

import boto3
 import operator

aws_account = 'XXXX'
 source = 'us-east-1'
 destination = 'sa-east-1'
 databases = ['mysqldb01', 'pgdb01']

def copy_latest_snapshot():
 client = boto3.client('rds', source)
 foreign_client = boto3.client('rds', destination)

response = client.describe_db_snapshots(
 SnapshotType='automated',
 IncludeShared=False,
 IncludePublic=False
 )

if len(response['DBSnapshots']) == 0:
 raise Exception("No automated snapshots found")

snapshots_per_project = {}

for snapshot in response['DBSnapshots']:
 if snapshot['DBInstanceIdentifier'] not in databases or snapshot['Status'] != 'available' :
 continue

if snapshot['DBInstanceIdentifier'] not in snapshots_per_project.keys():
 snapshots_per_project[snapshot['DBInstanceIdentifier']] = {}

snapshots_per_project[snapshot['DBInstanceIdentifier']][snapshot['DBSnapshotIdentifier']] = snapshot[
 'SnapshotCreateTime']

for project in snapshots_per_project:
 sorted_list = sorted(snapshots_per_project[project].items(), key=operator.itemgetter(1), reverse=True)

copy_name = project + "-" + sorted_list[0][1].strftime("%Y-%m-%d")

print("Checking if " + copy_name + " is copied")

try:
 foreign_client.describe_db_snapshots(
 DBSnapshotIdentifier=copy_name
 )
 except:
 response = foreign_client.copy_db_snapshot(
 SourceDBSnapshotIdentifier='arn:aws:rds:' + source + ':' + aws_account + ':snapshot:' + sorted_list[0][0],
 TargetDBSnapshotIdentifier=copy_name,
 CopyTags=True
 )

if response['DBSnapshot']['Status'] != "pending" and response['DBSnapshot']['Status'] != "available":
 raise Exception("Copy operation for " + copy_name + " failed!")
 print("Copied " + copy_name)

continue

print("Already copied")

def remove_old_snapshots():
 client = boto3.client('rds', source)
 foreign_client = boto3.client('rds', destination)

response = foreign_client.describe_db_snapshots(
 SnapshotType='manual'
 )

if len(response['DBSnapshots']) == 0:
 raise Exception("No manual snapshots in "+ destination + " found")

snapshots_per_project = {}
 for snapshot in response['DBSnapshots']:
 if snapshot['DBInstanceIdentifier'] not in databases or snapshot['Status'] != 'available' :
 continue

if snapshot['DBInstanceIdentifier'] not in snapshots_per_project.keys():
 snapshots_per_project[snapshot['DBInstanceIdentifier']] = {}

snapshots_per_project[snapshot['DBInstanceIdentifier']][snapshot['DBSnapshotIdentifier']] = snapshot[
 'SnapshotCreateTime']

for project in snapshots_per_project:
 if len(snapshots_per_project[project]) > 1:
 sorted_list = sorted(snapshots_per_project[project].items(), key=operator.itemgetter(1), reverse=True)
 to_remove = [i[0] for i in sorted_list[1:]]

for snapshot in to_remove:
 print("Removing " + snapshot)
 foreign_client.delete_db_snapshot(
 DBSnapshotIdentifier=snapshot
 )

def lambda_handler(event, context):
 copy_latest_snapshot()
 remove_old_snapshots()

if __name__ == '__main__':
 lambda_handler(None, None)

Referencia: Copying RDS snapshot to another region for cross-region recovery


, , ,

No hay Comentarios

Cómo agregar Apache HTTP Client en Android Studio

Android Studio - Apache HTTP Client

Para usar en Eclipse las librerías de Apache HTTP Client (httpclient y httpmime) simplemente bajaba el port para Android y luego incluía las dependencias especificando los archivos jar: /home/lgallard//Android/libs/httpcore-4.3.2.jar /home/lgallard//Android/libs/httpmime-4.3.5.jar Esta práctica la arrastré al migrar mis proyectos a Android Studio, pero el resto de las librerías si las resolvía con Gradle. Entonces para uniformizar todo, decidí investigar un poco cómo incluir las librerías de Apache HTTP Client. Basta con agregar las siguientes líneas en el archivo build.gradle de tu aplicación:


apply plugin: 'com.android.application'

android {

    dependencies {
        compile group: 'org.apache.httpcomponents' , name: 'httpclient-android' , version: '4.3.5.1'
        compile (group: 'org.apache.httpcomponents' , name: 'httpmime' , version: '4.3.5') {
        exclude module: 'org.apache.httpcomponents:httpclient'}
    }

    android {
        useLibrary 'org.apache.http.legacy'
    }

}

Finalmente sincroniza gradle y compila nuevamente tu proyecto.

Referencia: Apache HttpClient Android (Gradle)

 

,

18 Comentarios

De vuelta…

El Blog de Luis está de vuelta después de un pausa de varios meses, y gracias a la ayuda de una amiga que me está proporcionando el hosting del blog. Varias personas me preguntaron o pidieron información que solo conseguían en mi blog, por lo que decidí reactivarlo nuevamente.

Espero les sirva de ayuda, aprendan algo nuevo o sea una vía para intercambiar información.

¡Disfrútenlo!

, , ,

2 Comentarios

Cómo activar la interfaz Web de qBittorrent

Si deseas controlar to servidor qBittorrent usando la interfaz web, sigue los siguientes pasos:

  1. En la barra de menú, ve a Tools > Options qBittorrent WEB UI
  2. En la próxima ventana, selecciona la opción Web UI
  3. Marca la opción Enable the Web User Interface (Remote control)
  4. Selecciona un puerto (Por omisión 8080)
  5. Configura el nombre de usuario y contraseña (Por omisión username: admin / password: adminadmin) WEB UI
  6. Haz clic en Ok para guardar las configuraciones.

Ahora podrás acceder a tu servidor desde un navegador si colocas la dirección IP y puerto de tu servidor qBittorrent, ejemplo: 192.168.1.100:8080 como se muestra a continuación: qBittorrent Web UI También puedes acceder desde tu dispositivo Android si instalas qBittorrent Client o qBittorrent Client ProqBitttoren Client Pro

, ,

No hay Comentarios

Habilitar HTML5 para Blipblip.tv en plugin Video Sidebar Widgets de WordPress

El problema

Viendo mi blog desde una tableta me percaté que los videos de Blipblip.tv que configuré en el Video Sidebar Widgets no se mostraban. Investigando me di cuenta que el plugin carga la versión anterior del reproductor de Blipblip.tv basado en Flash y no HTML5, por lo que lo videos no se podían apreciar desde dispositivos móviles que no soportasen Flash, los cuales ya son la mayoría.

La solución

Simplemente en los archivos helper-functions.phpclass-videosidebarwidget.php agregué condicionales para el caso Blipblip.tv y en concordancia agregué el player con HTML5. En particular, agregué las siguientes líneas en el archivo helper-functions.php:

elseif($admin=="true"){
 if($source == "Blip"){
 echo "\n<iframe src=\"$value.html?p=1\" width=\"250\" height=\"250\" 
 frameborder=\"0\" allowfullscreen>
 </iframe> 
 
 <embed type=\"application/x-shockwave-flash\" src=\"http://a.blip.tv/api.swf#$v_id2\" 
 style=\"display:none\">
 </embed>\n\n"; 
 }else{ 
 // echo video in admin
 echo "\n<object width=\"212\" height=\"172\">\n";
 echo $flashvar;
 echo "<param name=\"allowfullscreen\" value=\"true\" />\n";
 echo "<param name=\"allowscriptaccess\" value=\"always\" />\n";
 echo "<param name=\"movie\" value=\"$value\" />\n";
 echo "<param name=\"wmode\" value=\"transparent\">\n";
 echo "<embed src=\"$value\" type=\"application/x-shockwave-flash\" wmode=\"transparent\" ";
 echo "allowfullscreen=\"true\" allowscriptaccess=\"always\" ";
 echo $flashvar2;
 echo "width=\"212\" height=\"172\">\n";
 echo "</embed>\n";
 echo "</object>\n\n";
 }
}else{

Y en el archivo class-videosidebarwidget.php modifiqué lo siguiente:

 case 'Blip':
 $rv_value = "http://blip.tv/play/$Embed_id";
 $rv_flashvar = "";
 $rv_flashvar2 = "";
 $rv_cap = $Embed_cap;

Y agregué estas líneas:

if($select_source == "Blip"){ 
 echo "\n<iframe align=\"left\" src=\"$rv_value.html?p=1\" width=\"$RV_width\" height=\"$RV_height\" 
 frameborder=\"0\" allowfullscreen>
 </iframe>
 
 <embed type=\"application/x-shockwave-flash\" src=\"http://a.blip.tv/api.swf#$Embed_id\" 
 style=\"display:none\">
 </embed>\n\n";
}else{
 echo "\n<object width=\"$RV_width\" height=\"$RV_height\">\n";
 echo $rv_flashvar;
 echo "<param name=\"allowfullscreen\" value=\"true\" />\n";
 echo "<param name=\"allowscriptaccess\" value=\"always\" />\n";
 echo "<param name=\"movie\" value=\"$rv_value\" />\n";
 echo "<param name=\"wmode\" value=\"transparent\">\n";
 echo "<embed src=\"$rv_value\" type=\"application/x-shockwave-flash\" wmode=\"transparent\" ";
 echo "allowfullscreen=\"true\" allowscriptaccess=\"always\" ";
 echo $rv_flashvar2;
 echo "width=\"$RV_width\" height=\"$RV_height\">\n";
 echo "</embed>\n";
 echo "</object>\n\n";
 }
 if(!empty($rv_cap)){echo "<p class=\"VideoCaption\">$rv_cap</p>\n\n";};
 
 
 echo $after_widget;
 }

Luego de esto, los videos aleatoreos con HTML5 se cargan sin problemas.

Soporte del plugin

Por cierto que contacté al desarrollador del plugin para que agregue estos cambios y me informó que estaba muy ocupado para hacerlo. Lástima que no lo sede al dominio público para que otro lo siga manteniendo.

,

No hay Comentarios

Curso Programming Mobile Applications for Android Handheld Systems

Programming Mobile Applications for Android Handheld Systems

Coursera me acaba de enviar mi certificado por haber culminado con ditinción el curso Programming Mobile Applications for Android Handheld Systems. Este curso es avalado por la universidad de Maryland y dictado por Dr. Adan Porter.

Algo interesante a resaltar sobre este curso de Android es lo bien estructurado que está, además de la calidad de los videos y las asignaciones. Me recordó mucho a algunos proyectos que tuve que realizar en mi universidad, ya que están bien organizados y con cosas específicas para realizar. De hecho la evaluación de los proyectos se realizan de forma automática con unos JUnits. El proyecto final si es evaluado por otros participantes del curso (4 en total).

Para este curso tome la opción del Signature Track, que es la modalidad de pago donde se valida que quien realiza las asignaciones es quien dice ser, usando un software de reconocimiento de patrón de escritura y con verificación de la foto del estudiante (al final del curso los asistentes del instructor verifican estas fotos). Con estas validaciones otorgan un certificado que puede ser verificado en las siguientes URLs:

Si estas interesado en este curso, puedes acceder al contenido del mismo en el enlace proporcionado arriba.

,

6 Comentarios

Respaldo y recuperación completo de GitLab con Postgresql

GitLab and Posgresql

GitLab tiene como la posibilidad de usar Postgresql como su motor de base de datos (a parte de MySQL) y todo viene ya listo para incluso hacer respaldos de su base de datos y sus repositorios Git haciendo uso de un script Ruby (un rake).  Esta es la forma recomendada ya que permite recuperar todo garantizando que no hay transacciones pendientes a nivel de base de datos ni de repositorios Git.

El  problema

En la documentación explican el detalle de cómo hacer un respaldo y restauración  manual, pero al momento de hacer la restauración esta falla porque al intentar insertar registros existentes estos generan conflictos como este:

ALTER SEQUENCE
psql:/home/git/gitlab/tmp/backups/db/database.sql:812: ERROR: relation "users" already exists

La solución

Se debe forzar el respaldo de pogresql para que primero borre las tablas para luego crearlas e insertar los registros en la restauración. Esto se consigue con la opción –clean o -c  de la orden pg_dump. Esta opción se debe editar en script ruby que realiza el respaldo, que por omisión es /home/git/gitlab/lib/backup/database.rb. En este archivo se debe ubicar esta porción del código y sustituir la línea en negrilla mostrada a continuación:

require 'yaml'
module Backup
 class Database
 attr_reader :config, :db_dir
def initialize
 @config = YAML.load_file(File.join(Rails.root,'config','database.yml'))[Rails.env]
 @db_dir = File.join(Gitlab.config.backup.path, 'db')
 FileUtils.mkdir_p(@db_dir) unless Dir.exists?(@db_dir)
 end
def dump
 success = case config["adapter"]
 when /^mysql/ then
 print "Dumping MySQL database #{config['database']} ... "
 system('mysqldump', *mysql_args, config['database'], out: db_file_name)
 when "postgresql" then
 print "Dumping PostgreSQL database #{config['database']} ... "
 pg_env
 system('pg_dump', config['database'], out: db_file_name)
 end
 report_success(success)
 end

Esta línea debe sustituirse por esta otra:

system('pg_dump', config['database'], '-c', out: db_file_name)

Aquí puede verse que la opción -c es pasada como argumento a la orden pg_dump. Esto hará que se incluyan todos los DROPS necesarios en el archivo .sql generado para el respaldo de GitLab.

,

No hay Comentarios

Curso Creative, Serious and Playful Science of Android Apps

Coursera androidapps101 2014 Computer Science and Programming Badge

Hace poco me llegaron los reconocimientos del curso de Android Creative, Serious and Playful Science of Android Apps dictado por Lawrence Angrave de la Universidad de Illinois en Urbana-Champaign. El curso está diseñado para aprender a desarrollar aplicaciones Android desde cero, por lo que si deseas aprender a desarrollar para esta plataforma este es un buen punto de partida. Los videos son en inglés, pero existen subtítulos en español e inglés, por lo que el idioma no es una barrera.

Los reconocimientos

Los reconocimientos que me otorgaron fueron:

  • Statement of Accomplishment: Esto es como un certificado de cumplimiento del curso con 70% o más. Yo obtuve 94.6%
  • Computer Science and Programming Badge: Esta es una especie de insignia que dan también por haber aprobado con más de 70%.

Estos reconocimientos no son los que dan con el Signature Track, el cual  permite compartir esta calificación con un sistema de validación comprobable, a cambio de $49 que se debe pagar para este beneficio.

,

No hay Comentarios

qBittorrent Controller para Android

Descarga la aplicación

Si estás interesado en esta aplicación puedes instalarla desde Google Play o descargando directamente el apk de qBittorrent Controller en tu dispositivo. También puedes descargar el código en GitHub ya que está liberado bajo licencia LGPL.

Características

  • Vista de dos páneles (fragments) para el listado de torrents y el detalle en la misma ventana
  • Menú desplegable para cambiar entre los listados de torrents: All, Downloading, Completed, Paused, Active and Inactive
  • Pausar o iniciar todos los torrents del listado actual
  • Actualización automática del listado después de ejecutar una acción en un torrent (pausar, iniciar, or delete)
  • Add URL directly or by clicking the torrent link on your device’s browser
  • Pause, resume, delete or delete individual torrents with its downloaded data
  • Set and save a connexion account

Capturas en teléfono



Capturas en tableta


,

6 Comentarios

Mi primera aplicación en Android: BatteryReporter

batteryreporter

Desde hace un tiempo estaba con la idea de aprender a desarrollar aplicaciones para Android, y en lo que vi que ofertaron un curso en Coursera de desarrollo de aplicaciones Android vi la oportunidad para hacerlo. Debo confesar que no es “copiar y pegar” (aunque se consigue códigos de ejemplos) y que se debe aprender nuevos conceptos, ya que aun y cuando hayas programado con anterioridad (incluso en Java) ahora debes aprender cómo se hace “a lo Android”.

Después de seguir los vídeos, leer y leer, ya hice mi primera aplicación en Android, que de hecho es parte de una asignación que exigen en el curso. Es una aplicación sencilla que lee el estado de la batería del dispositivo, y reporta si se está cargando así como el porcentaje de carga del mismo.

Para el que quiera probar mi primera aplicación, o simplemente echar una mirada al código por si quiere aprender, aquí dejo el archivo apk y el código del mismo.

El código (MainActivity.java)

Aquí les dejo el código java de la aplicación:

package com.lgallardo.batteryreporter;
import android.os.BatteryManager;
import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.view.Menu;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
public class MainActivity extends Activity {
@Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 }
@Override
 public boolean onCreateOptionsMenu(Menu menu) {
 // Inflate the menu; this adds items to the action bar if it is present.
 getMenuInflater().inflate(R.menu.main, menu);
 return true;
 }
public void getStatus(View view) {
TextView statusValueTextView, chargingValueTextView, levelValueTextView;
 ImageView iconImageView;

 String charging = "";
int level, scale;
 float batteryPct;
// Get resources reference
 Resources res = getResources();
// Get values TextViews
 statusValueTextView = (TextView) findViewById(R.id.statusValue);
 chargingValueTextView = (TextView) findViewById(R.id.chargingValue);
 levelValueTextView = (TextView) findViewById(R.id.levelValue);

 // Get ImageView (icon)

 iconImageView = (ImageView) findViewById(R.id.imageView1);

 // Get battery's status
 IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
 Intent batteryStatus = registerReceiver(null, ifilter);
// Check if the battery is charging or is charged?
 int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
 boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING
 || status == BatteryManager.BATTERY_STATUS_FULL;
// Update UI status
 statusValueTextView.setText(Integer.toString(status));
// Update UI charging
 if (isCharging) {
 charging = res.getString(R.string.yes);
// Get charging method
 int chargePlug = batteryStatus.getIntExtra(
 BatteryManager.EXTRA_PLUGGED, -1);
 boolean usbCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_USB;
 // boolean acCharge = chargePlug ==
 // BatteryManager.BATTERY_PLUGGED_AC;
if (usbCharge) {
 charging = charging + " " + res.getString(R.string.usb);
 } else {
 charging = charging + " " + res.getString(R.string.ac);
 }

 iconImageView.setImageResource(R.drawable.charging);
 } else {
 charging = res.getString(R.string.no);
 iconImageView.setImageResource(R.drawable.discharging);
 }
chargingValueTextView.setText(charging);
// Update UI level
 level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
 scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
 batteryPct = 100 * level / (float) scale;
levelValueTextView.setText(Float.toString(batteryPct)+"%");
}
}

,

2 Comentarios