[ChaosDatastore]Checkin latest version of the app engine code
Get all the working code and add date-util package to requirements and a README.
Bug: 112916493
Test: Tested locally and with the gcloud server
Change-Id: I5e8e31ef44bc0d6827de926d8d39e125653273f4
Reviewed-on: https://chromium-review.googlesource.com/1183818
Tested-by: Bindu Mahadev <bmahadev@chromium.org>
Reviewed-by: Bindu Mahadev <bmahadev@chromium.org>
Reviewed-by: Kris Rambish <krisr@chromium.org>
Commit-Queue: Bindu Mahadev <bmahadev@chromium.org>
Trybot-Ready: Bindu Mahadev <bmahadev@chromium.org>
diff --git a/provingground/chaosap/.gcloudignore b/provingground/chaosap/.gcloudignore
new file mode 100644
index 0000000..8e1329f
--- /dev/null
+++ b/provingground/chaosap/.gcloudignore
@@ -0,0 +1,16 @@
+# This file specifies files that are *not* uploaded to Google Cloud Platform
+# using gcloud. It follows the same syntax as .gitignore, with the addition of
+# "#!include" directives (which insert the entries of the given .gitignore-style
+# file at that point).
+#
+# For more information, run:
+# $ gcloud topic gcloudignore
+#
+.gcloudignore
+# If you would like to upload your .git directory, .gitignore file or files
+# from your .gitignore file, remove the corresponding line
+# below:
+.git
+.gitignore
+# Python pycache:
+__pycache__/
diff --git a/provingground/chaosap/.gitignore b/provingground/chaosap/.gitignore
new file mode 100644
index 0000000..3631dee
--- /dev/null
+++ b/provingground/chaosap/.gitignore
@@ -0,0 +1,2 @@
+lib/*
+venv/*
diff --git a/provingground/chaosap/README.txt b/provingground/chaosap/README.txt
new file mode 100644
index 0000000..25707db
--- /dev/null
+++ b/provingground/chaosap/README.txt
@@ -0,0 +1,12 @@
+Setup and deploy appengine.
+============================
+In the ./chaosp directory:
+1. Run the following command to install python dependencies:
+ # pip install -r requirements.txt -t lib
+
+2. To deploy the app engine:
+ # gcloud app deploy app.yaml
+
+3. To test changes using local server:
+ # dev_appserver.py app.yaml
+This will start a server on localport 8080.
diff --git a/provingground/chaosap/ap_spec.py b/provingground/chaosap/ap_spec.py
new file mode 100644
index 0000000..12f5f13
--- /dev/null
+++ b/provingground/chaosap/ap_spec.py
@@ -0,0 +1,306 @@
+# Copyright (c) 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Supported bands
+BAND_2GHZ = '2.4GHz'
+BAND_5GHZ = '5GHz'
+
+# List of valid bands.
+VALID_BANDS = [BAND_2GHZ, BAND_5GHZ]
+
+# List of valid 802.11 protocols (modes).
+MODE_A = 0x01
+MODE_B = 0x02
+MODE_G = 0x04
+MODE_N = 0x08
+MODE_AC = 0x10
+MODE_AUTO = 0x20
+MODE_M = MODE_A | MODE_B | MODE_G # Used for standard maintenance
+MODE_D = MODE_A | MODE_B | MODE_N # International roaming extensions
+MODE_B_G = MODE_B | MODE_G
+MODE_B_G_N = MODE_B | MODE_G | MODE_N
+MODE_AC_N = MODE_AC | MODE_N
+MODE_A_N = MODE_A | MODE_N
+
+# List of valid modes.
+VALID_MODES = [MODE_A, MODE_AC, MODE_AUTO, MODE_B, MODE_D, MODE_G, MODE_M,
+ MODE_N, MODE_B_G, MODE_B_G_N, MODE_A_N, MODE_AC_N]
+VALID_2GHZ_MODES = [MODE_B, MODE_G, MODE_N, MODE_B_G, MODE_B_G_N]
+VALID_5GHZ_MODES = [MODE_A, MODE_AC, MODE_N, MODE_A_N, MODE_AC_N]
+
+# Supported security types
+SECURITY_TYPE_DISABLED = 'open'
+SECURITY_TYPE_WEP = 'wep'
+SECURITY_TYPE_WPAPSK = 'wpa'
+SECURITY_TYPE_WPA2PSK = 'wpa2'
+# Mixed mode security is wpa/wpa2
+SECURITY_TYPE_MIXED = 'mixed'
+
+WEP_AUTHENTICATION_OPEN = object()
+WEP_AUTHENTICATION_SHARED = object()
+
+# List of valid securities.
+# TODO (krisr) the configurators do not support WEP at this time.
+VALID_SECURITIES = [SECURITY_TYPE_DISABLED,
+ SECURITY_TYPE_WPAPSK,
+ SECURITY_TYPE_WPA2PSK,
+ SECURITY_TYPE_MIXED,
+ SECURITY_TYPE_WEP]
+
+# List of valid channels.
+VALID_2GHZ_CHANNELS = range(1,15)
+VALID_5GHZ_CHANNELS = [36, 40, 44, 48, 149, 153, 157, 161, 165]
+
+# Frequency to channel conversion table
+CHANNEL_TABLE = {2412: 1, 2417: 2, 2422: 3,
+ 2427: 4, 2432: 5, 2437: 6,
+ 2442: 7, 2447: 8, 2452: 9,
+ 2457: 10, 2462: 11, 2467: 12,
+ 2472: 13, 2484: 14, 5180: 36,
+ 5200: 40, 5220: 44, 5240: 48,
+ 5745: 149, 5765: 153, 5785: 157,
+ 5805: 161, 5825: 165}
+
+# This only works because the frequency table is one to one
+# for channels/frequencies.
+FREQUENCY_TABLE = dict((v,k) for k,v in CHANNEL_TABLE.iteritems())
+
+# Configurator type
+CONFIGURATOR_STATIC = 1
+CONFIGURATOR_DYNAMIC = 2
+CONFIGURATOR_ANY = 3
+
+# Default values
+DEFAULT_BAND = BAND_2GHZ
+
+DEFAULT_2GHZ_MODE = MODE_G
+DEFAULT_5GHZ_MODE = MODE_A
+
+DEFAULT_SECURITY_TYPE = SECURITY_TYPE_DISABLED
+
+DEFAULT_2GHZ_CHANNEL = 5
+DEFAULT_5GHZ_CHANNEL = 149
+
+# Convenience method to convert modes and bands to human readable strings.
+def band_string_for_band(band):
+ """Returns a human readable string of the band
+
+ @param band: band object
+ @returns: string representation of the band
+ """
+ if band == BAND_2GHZ:
+ return '2.4 GHz'
+ elif band == BAND_5GHZ:
+ return '5 GHz'
+
+
+def mode_string_for_mode(mode):
+ """Returns a human readable string of the mode.
+
+ @param mode: integer, the mode to convert.
+ @returns: string representation of the mode
+ """
+ string_table = {MODE_A:'a', MODE_AC:'ac', MODE_B:'b', MODE_G:'g',
+ MODE_N:'n'}
+
+ if mode == MODE_AUTO:
+ return 'Auto'
+ total = 0
+ string = ''
+ for current_mode in sorted(string_table.keys()):
+ i = current_mode & mode
+ total = total | i
+ if i in string_table:
+ string = string + string_table[i] + '/'
+ if total == MODE_M:
+ string = 'm'
+ elif total == MODE_D:
+ string = 'd'
+ if string[-1] == '/':
+ return string[:-1]
+ return string
+
+
+class APSpec(object):
+ """Object to specify an APs desired capabilities.
+
+ The APSpec object is immutable. All of the parameters are optional.
+ For those not given the defaults listed above will be used. Validation
+ is done on the values to make sure the spec created is valid. If
+ validation fails a ValueError is raised.
+ """
+
+
+ def __init__(self, visible=True, security=SECURITY_TYPE_DISABLED,
+ band=None, mode=None, channel=None, hostnames=None,
+ configurator_type=CONFIGURATOR_ANY,
+ # lab_ap set to true means the AP must be in the lab;
+ # if it set to false the AP is outside of the lab.
+ lab_ap=True):
+ super(APSpec, self).__init__()
+ self._visible = visible
+ self._security = security
+ self._mode = mode
+ self._channel = channel
+ self._hostnames = hostnames
+ self._configurator_type = configurator_type
+ self._lab_ap = lab_ap
+ self._webdriver_hostname = None
+
+ if not self._channel and (self._mode == MODE_N or not self._mode):
+ if band == BAND_2GHZ or not band:
+ self._channel = DEFAULT_2GHZ_CHANNEL
+ if not self._mode:
+ self._mode = DEFAULT_2GHZ_MODE
+ elif band == BAND_5GHZ:
+ self._channel = DEFAULT_5GHZ_CHANNEL
+ if not self._mode:
+ self._mode = DEFAULT_5GHZ_MODE
+ else:
+ raise ValueError('Invalid Band.')
+
+ self._validate_channel_and_mode()
+
+ if ((band == BAND_2GHZ and self._mode not in VALID_2GHZ_MODES) or
+ (band == BAND_5GHZ and self._mode not in VALID_5GHZ_MODES)):
+ raise ValueError('Conflicting band and modes/channels.')
+
+ self._validate_security()
+
+
+ def __str__(self):
+ return ('AP Specification:\n'
+ 'visible=%r\n'
+ 'security=%s\n'
+ 'band=%s\n'
+ 'mode=%s\n'
+ 'channel=%d\n'
+ 'password=%s' % (self._visible, self._security,
+ band_string_for_band(self.band),
+ mode_string_for_mode(self._mode),
+ self._channel, self._password))
+
+
+ @property
+ def password(self):
+ """Returns the password for password supported secured networks."""
+ return self._password
+
+
+
+ @property
+ def visible(self):
+ """Returns if the SSID is visible or not."""
+ return self._visible
+
+
+ @property
+ def security(self):
+ """Returns the type of security."""
+ return self._security
+
+
+ @property
+ def band(self):
+ """Return the band."""
+ if self._channel in VALID_2GHZ_CHANNELS:
+ return BAND_2GHZ
+ return BAND_5GHZ
+
+
+ @property
+ def mode(self):
+ """Return the mode."""
+ return self._mode
+
+
+ @property
+ def channel(self):
+ """Return the channel."""
+ return self._channel
+
+
+ @property
+ def frequency(self):
+ """Return the frequency equivalent of the channel."""
+ return FREQUENCY_TABLE[self._channel]
+
+
+ @property
+ def hostnames(self):
+ """Return the hostnames; this may be None."""
+ return self._hostnames
+
+
+ @property
+ def configurator_type(self):
+ """Returns the configurator type."""
+ return self._configurator_type
+
+
+ @property
+ def lab_ap(self):
+ """Returns if the AP should be in the lab or not."""
+ return self._lab_ap
+
+
+ @property
+ def webdriver_hostname(self):
+ """Returns locked webdriver hostname."""
+ return self._webdriver_hostname
+
+
+ @webdriver_hostname.setter
+ def webdriver_hostname(self, value):
+ """Sets webdriver_hostname to locked instance.
+
+ @param value: locked webdriver hostname
+
+ """
+ self._webdriver_hostname = value
+
+
+ def _validate_channel_and_mode(self):
+ """Validates the channel and mode selected are correct.
+
+ raises ValueError: if the channel or mode selected is invalid
+ """
+ if self._channel and self._mode:
+ if ((self._channel in VALID_2GHZ_CHANNELS and
+ self._mode not in VALID_2GHZ_MODES) or
+ (self._channel in VALID_5GHZ_CHANNELS and
+ self._mode not in VALID_5GHZ_MODES)):
+ raise ValueError('Conflicting mode/channel has been selected.')
+ elif self._channel:
+ if self._channel in VALID_2GHZ_CHANNELS:
+ self._mode = DEFAULT_2GHZ_MODE
+ elif self._channel in VALID_5GHZ_CHANNELS:
+ self._mode = DEFAULT_5GHZ_MODE
+ else:
+ raise ValueError('Invalid channel passed.')
+ else:
+ if self._mode in VALID_2GHZ_MODES:
+ self._channel = DEFAULT_2GHZ_CHANNEL
+ elif self._mode in VALID_5GHZ_MODES:
+ self._channel = DEFAULT_5GHZ_CHANNEL
+ else:
+ raise ValueError('Invalid mode passed.')
+
+
+ def _validate_security(self):
+ """Sets a password for security settings that need it.
+
+ raises ValueError: if the security setting passed is invalid.
+ """
+ if self._security == SECURITY_TYPE_DISABLED:
+ self._password = None
+ elif (self._security == SECURITY_TYPE_WPAPSK or
+ self._security == SECURITY_TYPE_WPA2PSK or
+ self._security == SECURITY_TYPE_MIXED):
+ self._password = 'chromeos'
+ elif (self._security==SECURITY_TYPE_WEP):
+ self._password = 'chros'
+ else:
+ raise ValueError('Invalid security passed.')
+
diff --git a/provingground/chaosap/app.yaml b/provingground/chaosap/app.yaml
index 1522c0b..f63c8df 100644
--- a/provingground/chaosap/app.yaml
+++ b/provingground/chaosap/app.yaml
@@ -6,4 +6,3 @@
- url: /.*
script: main.app
secure: always
- login: required
\ No newline at end of file
diff --git a/provingground/chaosap/appengine_config.py b/provingground/chaosap/appengine_config.py
index 49511fc..893e0bd 100644
--- a/provingground/chaosap/appengine_config.py
+++ b/provingground/chaosap/appengine_config.py
@@ -18,4 +18,3 @@
# Add any libraries installed in the "lib" folder.
vendor.add('lib')
# [END vendor]
-
diff --git a/provingground/chaosap/main.py b/provingground/chaosap/main.py
index 54aa42e..96cd619 100644
--- a/provingground/chaosap/main.py
+++ b/provingground/chaosap/main.py
@@ -68,6 +68,37 @@
new_device.put()
return jsonify(result=True, id=hostname)
+
+@app.route('/devices/<hostname>', methods=['PUT', 'GET'])
+def edit_ap_device(hostname):
+ """Get and/or edit an AP.
+
+ curl -i -H "Content-Type: application/json" <URL>/devices/<HOSTNAME>
+
+ curl -i -H "Content-Type: application/json" -X PUT -d '{"ap_label":"<AP_LABEL>"}' \
+ <URL>/devices/<HOSTNAME>
+
+ """
+ if request.method == 'GET':
+ 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)
+
+
@app.route('/devices/lock', methods=['PUT'])
def lock_devices():
"""
diff --git a/provingground/chaosap/requirements.txt b/provingground/chaosap/requirements.txt
index d0be1d6..0755008 100644
--- a/provingground/chaosap/requirements.txt
+++ b/provingground/chaosap/requirements.txt
@@ -3,3 +3,4 @@
Flask-RESTful==0.3.6
Flask-HTTPAuth==3.2.3
PassLib==1.7.1
+python-dateutil==2.7.3