Upgrading chaos app to python3 runtime
BUG=chromium:1032597
Change-Id: Iaee3f06cfdfc881a75f654dd789bf729e9cd37bc
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crostestutils/+/2399180
Reviewed-by: Dinesh Kumar Sunkara <dsunkara@google.com>
Reviewed-by: Aashutosh Kalyankar <aashutoshk@chromium.org>
Reviewed-by: Katherine Threlkeld <kathrelkeld@chromium.org>
Tested-by: Aashutosh Kalyankar <aashutoshk@chromium.org>
Auto-Submit: Aashutosh Kalyankar <aashutoshk@chromium.org>
Commit-Queue: Aashutosh Kalyankar <aashutoshk@chromium.org>
diff --git a/provingground/chaosap/app.yaml b/provingground/chaosap/app.yaml
index f63c8df..e743522 100644
--- a/provingground/chaosap/app.yaml
+++ b/provingground/chaosap/app.yaml
@@ -1,8 +1,5 @@
-runtime: python27
-api_version: 1
-threadsafe: true
+runtime: python38
handlers:
- url: /.*
- script: main.app
- secure: always
+ script: auto
diff --git a/provingground/chaosap/main.py b/provingground/chaosap/main.py
index b386d8a..f30408f 100644
--- a/provingground/chaosap/main.py
+++ b/provingground/chaosap/main.py
@@ -1,6 +1,4 @@
-#!flask/bin/python
-# -*- coding: utf-8 -*-
-# Copyright 2017 Google Inc.
+# Copyright 2020 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -14,19 +12,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import ap_spec
-import logging
-import ndb_json
from flask import Flask, jsonify, abort, request, make_response
-from google.appengine.ext import ndb
+from google.cloud import ndb
+from marshmallow import Schema, fields
app = Flask(__name__)
+client = ndb.Client()
-# Chaos AP Device Methods
class ApDevice(ndb.Model):
- """Models an individual AP object with specs."""
- hostname = ndb.StringProperty(indexed=True)
+ """ Model class to create AP entities to write to Datastore """
+ hostname = ndb.StringProperty()
lock_status = ndb.BooleanProperty(indexed=True)
lock_status_updated = ndb.DateTimeProperty(auto_now=True)
locked_by = ndb.StringProperty()
@@ -34,9 +30,25 @@
lab_label = ndb.StringProperty()
ap_label = ndb.StringProperty()
+
+class DataSerializer(Schema):
+
+ class Meta:
+ # The serialized output has all the items defined in fields param
+ fields = (
+ "id", "hostname", "lock_status", "lock_status_updated",
+ "locked_by", "router_name", "lab_label", "ap_label"
+ )
+
+
+ndb_serialize = DataSerializer()
+multi_ndb_serialize = DataSerializer(many=True)
+
+
@app.route('/')
def greeting():
- return 'Welcome to Chaos AP Page.\n'
+ return "Welcome to Chaos AP Page"
+
@app.route('/devices/new', methods=['POST'])
def post():
@@ -50,24 +62,23 @@
@returns string if success, error 400 if fails.
"""
if not request.json:
- abort(400)
- hostname = request.json['hostname']
- new_device = ApDevice(
- id = hostname,
- hostname = hostname,
- lock_status = False)
- if 'router_name' in request.json and type(request.json['router_name']) != unicode:
- make_response(jsonify({'error': 'Router name not unicode.'}), 400)
- elif 'ap_label' in request.json and type(request.json['ap_label']) != unicode:
- make_response(jsonify({'error': 'AP label not unicode.'}), 400)
- elif 'lab_label' in request.json and type(request.json['lab_label']) != unicode:
- make_response(jsonify({'error': 'Lab label not unicode.'}), 400)
- else:
+ abort(400, "Not a JSON request")
+ required_keys = ('hostname', 'router_name', 'ap_label', 'lab_label')
+ for _key in required_keys:
+ if _key in request.json and type(request.json[_key]) != str:
+ return make_response(jsonify({'error': f'{_key} value not a str'}))
+
+ with client.context():
+ hostname = request.json['hostname']
+ new_device = ApDevice(
+ id=hostname,
+ hostname=hostname,
+ lock_status=False)
new_device.router_name = request.json['router_name']
new_device.ap_label = request.json['ap_label']
new_device.lab_label = request.json['lab_label']
- new_device.put()
- return jsonify(result=True, id=hostname)
+ new_device.put()
+ return jsonify(result=True, id=hostname)
@app.route('/devices/<hostname>', methods=['PUT', 'GET'])
@@ -80,24 +91,21 @@
<URL>/devices/<HOSTNAME>
"""
- if request.method == 'GET':
+ with client.context():
device_key = ndb.Key(ApDevice, hostname)
device = device_key.get()
- device_js = ndb_json.dumps(device, indent=0)
- return device_js
-
- if request.method == 'PUT':
- device_key = ndb.Key(ApDevice, hostname)
- device = device_key.get()
- if 'router_name' in request.json:
- device.router_name = request.json['router_name']
- if 'ap_label' in request.json:
- device.ap_label = request.json['ap_label']
- if 'lab_label' in request.json:
- device.lab_label = request.json['lab_label']
- device.put()
- return jsonify(result=True, id=hostname, ap_label=device.ap_label, \
- lab_label=device.lab_label, router_name=device.router_name)
+ if request.method == 'GET':
+ return jsonify(ndb_serialize.dump(device))
+ if request.method == 'PUT':
+ if 'router_name' in request.json:
+ device.router_name = request.json['router_name']
+ if 'ap_label' in request.json:
+ device.ap_label = request.json['ap_label']
+ if 'lab_label' in request.json:
+ device.lab_label = request.json['lab_label']
+ device.put()
+ return jsonify(result=True, id=hostname, ap_label=device.ap_label,
+ lab_label=device.lab_label, router_name=device.router_name)
@app.route('/devices/lock', methods=['PUT'])
@@ -112,27 +120,31 @@
@returns Hosts locked message if success, error 400 if fails.
"""
if not request.json:
- abort(400)
- if type(request.json['hostname']) is list:
- hostnames = request.json['hostname']
- device_keys = [ndb.Key(ApDevice, x) for x in hostnames]
- devices = ndb.get_multi(device_keys)
- for d in devices:
- d.lock_status = True
- ndb.put_multi(devices)
- return jsonify(result=True, id=hostnames)
- elif 'hostname' in request.json and type(request.json['hostname']) != unicode:
- return make_response(jsonify({'error': 'Hostname not unicode.'}), 400)
- elif 'locked_by' in request.json and type(request.json['locked_by']) != unicode:
- return make_response(jsonify({'error': 'Need locked_by name'}), 400)
- else:
+ abort(400, "Not a JSON request")
+ with client.context():
hostname = request.json['hostname']
- device_key = ndb.Key(ApDevice, hostname)
- device = device_key.get()
- device.lock_status = True
- device.locked_by = request.json['locked_by']
- device.put()
- return jsonify(result=True, id=hostname)
+ if type(hostname) is list:
+ hostnames = hostname
+ device_keys = [ndb.Key(ApDevice, x) for x in hostnames]
+ devices = ndb.get_multi(device_keys)
+ for d in devices:
+ d.lock_status = True
+ d.locked_by = request.json['locked_by']
+ ndb.put_multi(devices)
+ return jsonify(result=True, id=hostnames)
+ elif 'hostname' in request.json and type(request.json['hostname']) != str:
+ return make_response(jsonify({'error': 'Hostname not string'}), 400)
+ elif 'locked_by' in request.json and type(request.json['locked_by']) != str:
+ return make_response(jsonify({'error': 'locked_by name not string'}), 400)
+ else:
+ hostname = request.json['hostname']
+ device_key = ndb.Key(ApDevice, hostname)
+ device = device_key.get()
+ device.lock_status = True
+ device.locked_by = request.json['locked_by']
+ device.put()
+ return jsonify(result=True, id=hostname)
+
@app.route('/devices/unlock', methods=['PUT'])
def unlock_devices():
@@ -146,24 +158,28 @@
"""
if not request.json:
return make_response(jsonify({'error': 'Need json dict.'}), 400)
- elif 'hostname' in request.json and type(request.json['hostname']) != unicode:
- return make_response(jsonify({'error': 'Hostname not unicode.'}), 400)
- elif type(request.json['hostname']) is list:
- hostnames = request.json['hostname']
- device_keys = [ndb.Key(ApDevice, x) for x in hostnames]
- devices = ndb.get_multi(device_keys)
- for d in devices:
- d.lock_status = False
- ndb.put_multi(devices)
- return jsonify(result=True, id=hostnames)
- else:
+ with client.context():
hostname = request.json['hostname']
- device_key = ndb.Key(ApDevice, hostname)
- device = device_key.get()
- device.lock_status = False
- device.locked_by = None
- device.put()
- return jsonify(result=True, id=hostname)
+ if type(hostname) is list:
+ hostnames = hostname
+ device_keys = [ndb.Key(ApDevice, x) for x in hostnames]
+ devices = ndb.get_multi(device_keys)
+ for d in devices:
+ d.lock_status = False
+ d.locked_by = None
+ ndb.put_multi(devices)
+ return jsonify(result=True, id=hostnames)
+
+ elif 'hostname' in request.json and type(request.json['hostname']) != str:
+ return make_response(jsonify({'error': 'Hostname not string'}), 400)
+ else:
+ device_key = ndb.Key(ApDevice, hostname)
+ device = device_key.get()
+ device.lock_status = False
+ device.locked_by = None
+ device.put()
+ return jsonify(result=True, id=hostname)
+
@app.route('/devices/delete', methods=['PUT'])
def delete():
@@ -174,13 +190,15 @@
@returns Hosts deleted message if success.
"""
- hostname = request.json['hostname']
- device_key = ndb.Key(ApDevice, hostname)
- device_key.delete()
- return "%s deleted.\n" % hostname
+ with client.context():
+ hostname = request.json['hostname']
+ device_key = ndb.Key(ApDevice, hostname)
+ device_key.delete()
+ return "%s deleted.\n" % hostname
# Querying devices.
+
@app.route('/devices/', methods=['GET'])
def get_devices():
"""Get list of devices in datastore.
@@ -189,9 +207,10 @@
@returns list of devices
"""
- devices_query = ApDevice.query()
- devices_entity = ndb_json.dumps(devices_query, indent=0, sort_keys=True)
- return devices_entity
+ with client.context():
+ devices_list = ApDevice.query().fetch()
+ return jsonify(multi_ndb_serialize.dump(devices_list))
+
@app.route('/devices/<hostname>', methods=['GET'])
def show_device(hostname):
@@ -201,10 +220,11 @@
@returns python dict of device
"""
- device_key = ndb.Key(ApDevice, hostname)
- device = device_key.get()
- device_js = ndb_json.dumps(device, indent=0)
- return device_js
+ with client.context():
+ device_key = ndb.Key(ApDevice, hostname)
+ device = device_key.get()
+ return jsonify(ndb_serialize.dump(device))
+
@app.route('/unlocked_devices/', methods=['GET'])
def get_unlocked_devices():
@@ -214,9 +234,10 @@
@returns list of devices
"""
- devices_query = ApDevice.query(ApDevice.lock_status==False)
- devices_entity = ndb_json.dumps(devices_query, indent=0, sort_keys=True)
- return devices_entity
+ with client.context():
+ devices_list = ApDevice.query(ApDevice.lock_status == False).fetch()
+ return jsonify(multi_ndb_serialize.dump(devices_list))
+
@app.route('/devices/location', methods=['PUT'])
def filter_by_location():
@@ -229,16 +250,17 @@
"""
if not request.json:
return make_response(jsonify({'error': 'Need json dict.'}), 400)
+ with client.context():
+ devices_list = ApDevice.query(ApDevice.ap_label == request.json["ap_label"],
+ ApDevice.lab_label == request.json["lab_label"],
+ ApDevice.lock_status == False).fetch()
+ return jsonify(multi_ndb_serialize.dump(devices_list))
- devices_query = ApDevice.query(ApDevice.ap_label==request.json["ap_label"],
- ApDevice.lab_label==request.json["lab_label"],
- ApDevice.lock_status==False)
- devices_entity = ndb_json.dumps(devices_query, indent=0, sort_keys=True)
- return devices_entity
@app.errorhandler(404)
def not_found(error):
return make_response(jsonify({'error': 'Not found'}), 404)
+
if __name__ == '__main__':
app.run(host='127.0.0.1', port=8080, debug=True)
diff --git a/provingground/chaosap/ndb_json.py b/provingground/chaosap/ndb_json.py
deleted file mode 100644
index 0437eb4..0000000
--- a/provingground/chaosap/ndb_json.py
+++ /dev/null
@@ -1,166 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""
-JSON encoder/decoder adapted for use with Google App Engine NDB.
-Usage:
- import ndb_json
-
- # Serialize an ndb.Query into an array of JSON objects.
- query = models.MyModel.query()
- query_json = ndb_json.dumps(query)
-
- # Convert into a list of Python dictionaries.
- query_dicts = ndb_json.loads(query_json)
-
- # Serialize an ndb.Model instance into a JSON object.
- entity = query.get()
- entity_json = ndb_json.dumps(entity)
-
- # Convert into a Python dictionary.
- entity_dict = ndb_json.loads(entity_json)
-Dependencies:
- - dateutil: https://pypi.python.org/pypi/python-dateutil
-"""
-
-__author__ = 'Eric Higgins'
-__copyright__ = 'Copyright 2013, Eric Higgins'
-__version__ = '0.0.5'
-__email__ = 'erichiggins@gmail.com'
-__status__ = 'Development'
-
-
-import base64
-import datetime
-import json
-import re
-import time
-import types
-
-import dateutil.parser
-from google.appengine.ext import ndb
-
-
-def encode_model(obj):
- """Encode objects like ndb.Model which have a `.to_dict()` method."""
- obj_dict = obj.to_dict()
- for key, val in obj_dict.iteritems():
- if isinstance(val, types.StringType):
- try:
- unicode(val)
- except UnicodeDecodeError:
- # Encode binary strings (blobs) to base64.
- obj_dict[key] = base64.b64encode(val)
- return obj_dict
-
-
-def encode_generator(obj):
- """Encode generator-like objects, such as ndb.Query."""
- return list(obj)
-
-
-def encode_key(obj):
- """Get the Entity from the ndb.Key for further encoding."""
- # Note(eric): Potentially poor performance for Models w/ many KeyProperty properties.
- return obj.get_async()
- # Alternative 1: Convert into pairs.
- #return obj.pairs()
- # Alternative 2: Convert into URL-safe base64-encoded string.
- #return obj.urlsafe()
-
-
-def encode_future(obj):
- """Encode an ndb.Future instance."""
- return obj.get_result()
-
-
-def encode_datetime(obj):
- """Encode a datetime.datetime or datetime.date object as an ISO 8601 format string."""
- # Reformat the date slightly for better JS compatibility.
- # Offset-naive dates need 'Z' appended for JS.
- # datetime.date objects don't have or need tzinfo, so don't append 'Z'.
- zone = '' if getattr(obj, 'tzinfo', True) else 'Z'
- return obj.isoformat() + zone
-
-
-def encode_complex(obj):
- """Convert a complex number object into a list containing the real and imaginary values."""
- return [obj.real, obj.imag]
-
-
-def encode_basevalue(obj):
- """Retrieve the actual value from a ndb.model._BaseValue.
-
- This is a convenience function to assist with the following issue:
- https://code.google.com/p/appengine-ndb-experiment/issues/detail?id=208
- """
- return obj.b_val
-
-
-NDB_TYPE_ENCODING = {
- ndb.MetaModel: encode_model,
- ndb.Query: encode_generator,
- ndb.QueryIterator: encode_generator,
- ndb.Key: encode_key,
- ndb.Future: encode_future,
- datetime.date: encode_datetime,
- datetime.datetime: encode_datetime,
- time.struct_time: encode_generator,
- types.ComplexType: encode_complex,
- ndb.model._BaseValue: encode_basevalue,
-
-}
-
-
-class NdbEncoder(json.JSONEncoder):
- """Extend the JSON encoder to add support for NDB Models."""
-
- def default(self, obj):
- """Overriding the default JSONEncoder.default for NDB support."""
-
- obj_type = type(obj)
- # NDB Models return a repr to calls from type().
- if obj_type not in NDB_TYPE_ENCODING and hasattr(obj, '__metaclass__'):
- obj_type = obj.__metaclass__
- fn = NDB_TYPE_ENCODING.get(obj_type)
- if fn:
- return fn(obj)
-
- return json.JSONEncoder.default(self, obj)
-
-
-def dumps(ndb_model, **kwargs):
- """Custom json dumps using the custom encoder above."""
- return NdbEncoder(**kwargs).encode(ndb_model)
-
-
-def dump(ndb_model, fp, **kwargs):
- """Custom json dump using the custom encoder above."""
- for chunk in NdbEncoder(**kwargs).iterencode(ndb_model):
- fp.write(chunk)
-
-
-def loads(json_str, **kwargs):
- """Custom json loads function that converts datetime strings."""
- json_dict = json.loads(json_str, **kwargs)
- if isinstance(json_dict, list):
- return map(iteritems, json_dict)
- return iteritems(json_dict)
-
-
-def iteritems(json_dict):
- """Loop over a json dict and try to convert strings to datetime."""
- for key, val in json_dict.iteritems():
- if isinstance(val, dict):
- iteritems(val)
- # Its a little hacky to check for specific chars, but avoids integers.
- elif isinstance(val, basestring) and 'T' in val:
- try:
- json_dict[key] = dateutil.parser.parse(val)
- # Check for UTC.
- if val.endswith(('+00:00', '-00:00', 'Z')):
- # Then remove tzinfo for gae, which is offset-naive.
- json_dict[key] = json_dict[key].replace(tzinfo=None)
- except (TypeError, ValueError):
- pass
- return json_dict
diff --git a/provingground/chaosap/requirements.txt b/provingground/chaosap/requirements.txt
index 0755008..c8fcfcb 100644
--- a/provingground/chaosap/requirements.txt
+++ b/provingground/chaosap/requirements.txt
@@ -1,6 +1,4 @@
-Flask==0.12.2
-oauth2client==4.1.2
-Flask-RESTful==0.3.6
-Flask-HTTPAuth==3.2.3
-PassLib==1.7.1
-python-dateutil==2.7.3
+Flask==1.1.2
+firebase-admin==4.3.0
+google-cloud-ndb==1.5.1
+marshmallow==3.7.1