'use strict';
/**
* The Python store environment.
*
* @module env
* @submodule env.python
*/
YUI.add('juju-env-python', function(Y) {
var environments = Y.namespace('juju.environments');
var endpointToName = function(endpoint) {
return endpoint[0] + ':' + endpoint[1].name;
};
/**
* The Python Juju environment.
*
* This class handles the websocket connection to the PyJuju API backend.
*
* @class PythonEnvironment
*/
function PythonEnvironment(config) {
// Invoke Base constructor, passing through arguments.
PythonEnvironment.superclass.constructor.apply(this, arguments);
}
PythonEnvironment.NAME = 'python-env';
Y.extend(PythonEnvironment, environments.BaseEnvironment, {
/**
* Python environment constructor.
*
* @method initializer
* @return {undefined} Nothing.
*/
initializer: function() {
// When the server tells us the outcome of a login attempt we record
// the result.
this.on('login', this.handleLoginEvent, this);
// Define the default user name for this environment. It will appear as
// predefined value in the login mask.
this.defaultUser = 'admin';
},
/**
* Fire a "msg" event when a message is received from the WebSocket.
* Handle the initial handshake with the server.
* The "evt.data.ready" attribute indicates the server's initial greeting.
* It provides a few initial values that we care about.
*
* @method on_message
* @param {Object} evt The event triggered by the WebSocket.
* @return {undefined} Side effects only.
*/
on_message: function(evt) {
var msg = Y.JSON.parse(evt.data);
if (msg.ready) {
this.set('providerType', msg.provider_type);
this.set('defaultSeries', msg.default_series);
return;
}
this.fire('msg', msg);
},
/**
* React to the results of sending a login message to the server.
*
* @method handleLoginEvent
* @param {Object} evt The event to which we are responding.
* @return {undefined} Nothing.
*/
handleLoginEvent: function(evt) {
this.userIsAuthenticated = !!evt.data.result;
// If the credentials were rejected remove them.
if (!this.userIsAuthenticated) {
this.setCredentials(null);
this.failedAuthentication = true;
}
},
_dispatch_event: function(evt) {
if (!('op' in evt)) {
console.warn('Env: Unknown evt kind', evt);
return;
}
this.fire(evt.op, {data: evt});
},
_dispatch_rpc_result: function(msg) {
if ('request_id' in msg) {
var tid = msg.request_id;
if (tid in this._txn_callbacks) {
this._txn_callbacks[tid].apply(this, [msg]);
delete this._txn_callbacks[tid];
}
}
},
/**
* Send a message to the server using the websocket connection.
*
* @method _send_rpc
* @private
* @param {Object} op The operation to perform (with an "op" attr).
* @param {Function} callback A callable that must be called once the
backend returns results.
* @param {Boolean} writePermissionRequired Whether the requested
operation requires write permission, i.e. it modifies the env.
* @return {undefined} Sends a message to the server only.
*/
_send_rpc: function(op, callback, writePermissionRequired) {
// Avoid sending remote messages if the operation requires writing
// and the GUI is in read-only mode.
if (writePermissionRequired && this.get('readOnly')) {
this._firePermissionDenied(op);
// Execute the callback passing an event-like object containing an
// error.
if (callback) {
callback(Y.merge(op, {err: true}));
}
return;
}
var tid = this._counter += 1;
if (callback) {
this._txn_callbacks[tid] = callback;
}
op.request_id = tid;
var msg = Y.JSON.stringify(op);
this.ws.send(msg);
},
// PythonEnvironment API
/**
* Add units to the provided service.
*
* @method add_unit
* @param {String} service The service to be scaled up.
* @param {Integer} num_units The number of units to be added.
* @param {Function} callback A callable that must be called once the
operation is performed.
* @return {undefined} Sends a message to the server only.
*/
add_unit: function(service, num_units, callback) {
this._send_rpc({
'op': 'add_unit',
'service_name': service,
'num_units': num_units}, callback, true);
},
/**
* Remove units from a service.
*
* @method remove_units
* @param {Array} unit_names The units to be removed.
* @param {Function} callback A callable that must be called once the
operation is performed.
* @return {undefined} Sends a message to the server only.
*/
remove_units: function(unit_names, callback) {
this._send_rpc({
'op': 'remove_units',
'unit_names': unit_names}, callback, true);
},
/**
* Add a relation between two services.
*
* @method add_relation
* @param {Object} endpointA An array of [service, interface] representing
the first endpoint to connect.
* @param {Object} endpointB An array of [service, interface] representing
the second endpoint to connect.
* @param {Function} callback A callable that must be called once the
operation is performed.
* @return {undefined} Sends a message to the server only.
*/
add_relation: function(endpointA, endpointB, callback) {
this._send_rpc({
'op': 'add_relation',
'endpoint_a': endpointToName(endpointA),
'endpoint_b': endpointToName(endpointB)}, callback, true);
},
/**
Retrieve charm info.
@method get_charm
@param {String} charmURL The URL of the charm.
@param {Function} callback A callable that must be called once the
operation is performed. It will receive an object with an "err"
attribute containing a string describing the problem (if an error
occurred), and with a "result" attribute containing information
about the charm. The "result" object includes "config" options, a list
of "peers", "provides" and "requires", and the charm URL.
@return {undefined} Sends a message to the server only.
*/
get_charm: function(charmURL, callback) {
this._send_rpc({op: 'get_charm', charm_url: charmURL}, callback);
},
/**
Get the configuration for the given service.
@method get_service
@param {String} service_name The service name.
@param {Function} callback A callable that must be called once the
operation is performed. It will receive an object containing:
err - a string describing the problem (if an error occurred),
service_name - the name of the service,
result: an object containing all of the configuration data for
the service.
@return {undefined} Sends a message to the server only.
*/
get_service: function(service_name, callback) {
this._send_rpc(
{'op': 'get_service', 'service_name': service_name}, callback);
},
/**
* Deploy a charm.
*
* @method deploy
* @param {String} charm_url The URL of the charm.
* @param {String} service_name The name of the service to be deployed.
* @param {Object} config The charm configuration options.
* @param {String} config_raw The YAML representation of the charm
configuration options. Only one of `config` and `config_raw` should be
provided, though `config_raw` takes precedence if it is given.
* @param {Integer} num_units The number of units to be deployed.
* @param {Function} callback A callable that must be called once the
operation is performed.
* @return {undefined} Sends a message to the server only.
*/
deploy: function(charm_url, service_name, config, config_raw, num_units,
callback) {
this._send_rpc(
{ op: 'deploy',
service_name: service_name,
config: config,
config_raw: config_raw,
charm_url: charm_url,
num_units: num_units},
callback, true);
},
/**
* Expose the given service.
*
* @method expose
* @param {String} service The service name.
* @param {Function} callback A callable that must be called once the
operation is performed.
* @return {undefined} Sends a message to the server only.
*/
expose: function(service, callback) {
this._send_rpc(
{'op': 'expose', 'service_name': service}, callback, true);
},
/**
* Attempt to log the user in. Credentials must have been previously
* stored on the environment.
*
* @method login
* @return {undefined} Nothing.
*/
login: function() {
// If the user is already authenticated there is nothing to do.
if (this.userIsAuthenticated) {
return;
}
var credentials = this.getCredentials();
if (credentials && credentials.areAvailable) {
this._send_rpc({
op: 'login',
user: credentials.user,
password: credentials.password
});
} else {
console.warn('Attempted login without providing credentials.');
this.fire('login', { data: { result: false } });
}
},
/**
* Un-expose the given service.
*
* @method unexpose
* @param {String} service The service name.
* @param {Function} callback A callable that must be called once the
operation is performed.
* @return {undefined} Sends a message to the server only.
*/
unexpose: function(service, callback) {
this._send_rpc(
{'op': 'unexpose', 'service_name': service}, callback, true);
},
/**
* Remove a relation between two services.
*
* @method remove_relation
* @param {Object} endpointA An array of [service, interface] representing
the first endpoint to disconnect.
* @param {Object} endpointB An array of [service, interface] representing
the second endpoint to disconnect.
* @param {Function} callback A callable that must be called once the
operation is performed.
* @return {undefined} Sends a message to the server only.
*/
remove_relation: function(endpointA, endpointB, callback) {
this._send_rpc({
'op': 'remove_relation',
'endpoint_a': endpointToName(endpointA),
'endpoint_b': endpointToName(endpointB)}, callback, true);
},
/**
* Destroy the given service.
*
* @method destroy_service
* @param {String} service The service name.
* @param {Function} callback A callable that must be called once the
operation is performed.
* @return {undefined} Sends a message to the server only.
*/
destroy_service: function(service, callback) {
this._send_rpc({
'op': 'destroy_service',
'service_name': service}, callback, true);
},
/**
* Change the configuration of the given service.
*
* @method set_config
* @param {String} service The service name.
* @param {Object} config The charm configuration options.
* @param {String} data The YAML representation of the charm
configuration options. Only one of `config` and `data` should be
provided, though `data` takes precedence if it is given.
* @param {Function} callback A callable that must be called once the
operation is performed.
* @return {undefined} Sends a message to the server only.
*/
set_config: function(service, config, data, callback) {
this._send_rpc({
op: 'set_config',
service_name: service,
config: config,
data: data}, callback, true);
},
// The constraints that the backend understands. Used to generate forms.
genericConstraints: ['cpu', 'mem', 'arch'],
/**
* Change the constraints of the given service.
*
* @method set_constraints
* @param {String} service The service name.
* @param {Object} constraints A hash of charm constraints.
* @param {Function} callback A callable that must be called once the
operation is performed.
* @return {undefined} Sends a message to the server only.
*/
set_constraints: function(service, constraints, callback) {
// Transform the constraints mapping into a string the backend
// understands.
var values = [];
Y.Object.each(constraints, function(value, name) {
values.push(name + '=' + value);
});
this._send_rpc({
op: 'set_constraints',
service_name: service,
constraints: values}, callback, true);
},
/**
* Mark the given unit or relation problem as resolved.
*
* @method resolved
* @param {String} unit_name The unit name.
* @param {String} relation_name The relation name.
* @param {Boolean} retry Whether or not to retry the unit/relation.
* @param {Function} callback A callable that must be called once the
operation is performed.
* @return {undefined} Sends a message to the server only.
*/
resolved: function(unit_name, relation_name, retry, callback) {
this._send_rpc({
op: 'resolved',
unit_name: unit_name,
relation_name: relation_name || null,
retry: retry || false}, callback, true);
},
/**
* Update the annotations for an entity by name.
*
* @param {Object} entity The name of a machine, unit, service, or
* environment, e.g. '0', 'mysql/0', or 'mysql'. To specify the
* environment as the entity the magic string 'env' is used.
* @param {String} type The type of the entity; not used, but required
* for Go compatibility.
* @param {Object} data A dictionary of key, value pairs.
* @return {undefined} Nothing.
* @method update_annotations
*/
update_annotations: function(entity, type, data, callback) {
this._send_rpc({
op: 'update_annotations',
entity: entity,
data: data}, callback, true);
},
/**
* Get the annotations for an entity by name.
*
* Note that the annotations are returned as part of the delta stream, so
* the explicit use of this command should rarely be needed.
*
* @param {Object} entity The name of a machine, unit, service, or
* environment, e.g. '0', 'mysql/0', or 'mysql'. To specify the
* environment as the entity the magic string 'env' is used.
* @param {String} type The type of the entity; not used, but required
* for Go compatibility.
* @return {Object} A dictionary of key,value pairs is returned in the
* callback. The invocation of this command returns nothing.
* @method get_annotations
*/
get_annotations: function(entity, type, callback) {
this._send_rpc({
op: 'get_annotations',
entity: entity}, callback);
},
/**
* Remove the annotations for an entity by name.
*
* @param {Object} entity The name of a machine, unit, service, or
* environment, e.g. '0', 'mysql/0', or 'mysql'. To specify the
* environment as the entity the magic string 'env' is used.
* @param {String} type The type of the entity; not used, but required
* for Go compatibility.
* @param {Object} keys An optional list of annotation key names for the
* annotations to be deleted. If no keys are passed, all annotations
* for the entity will be removed.
* @return {undefined} Nothing.
* @method remove_annotations
*/
remove_annotations: function(entity, type, keys, callback) {
this._send_rpc({
op: 'remove_annotations',
entity: entity,
keys: keys || []}, callback, true);
}
});
environments.PythonEnvironment = PythonEnvironment;
}, '0.1.0', {
requires: [
'base',
'json-parse',
'json-stringify',
'juju-env-base'
]
});