refactor joinPlan, leavePlan; Node, ComponentType, Plan, Plugin interface

This commit is contained in:
jysperm
2014-12-15 15:25:12 +08:00
parent 7e2e5616bc
commit d86d33e3bd
12 changed files with 292 additions and 212 deletions

View File

@@ -95,7 +95,6 @@ app.mailer = nodemailer.createTransport config.email.account
app.express = express()
app.models = {}
app.classes = {}
app.config = config
app.db = require './core/db'
@@ -113,10 +112,15 @@ require './core/model/Component'
app.templates = require './core/templates'
app.billing = require './core/billing'
app.clusters = require './core/clusters'
app.middleware = require './core/middleware'
app.notification = require './core/notification'
app.interfaces =
Node: require './core/interface/Node'
Plan: require './core/interface/Plan'
ComponentType: require './core/interface/ComponentType'
Plugin: require './core/interface/Plugin'
app.express.use bodyParser.json()
app.express.use cookieParser()
@@ -137,9 +141,9 @@ app.express.use '/coupon', require './core/router/coupon'
app.express.use '/admin', require './core/router/admin'
app.express.use '/panel', require './core/router/panel'
app.billing.initPlans()
app.clusters.initNodes()
app.i18n.init()
app.interfaces.Plan.initPlans()
app.interfaces.Node.initNodes()
app.pluggable.initPlugins()
app.express.get '/', (req, res) ->

View File

@@ -1,9 +1,8 @@
{config, pluggable, logger} = app
{async, _} = app.libs
{Account, Financials} = app.models
{Account, Financials, Component} = app.models
billing = exports
billing.plans = {}
billing.start = ->
Account.find
@@ -84,71 +83,6 @@ exports.generateBilling = (account, plan_name, callback) ->
last_billing_at: new_last_billing_at
amount_inc: -amount
exports.joinPlan = (req, account, plan_name, callback) ->
original_account = account
plan_info = config.plans[plan_name]
modifier =
$addToSet:
'billing.plans': plan_name
'billing.services':
$each: plan_info.services
$set:
'resources_limit': exports.calcResourcesLimit _.union account.billing.plans, [plan_name]
modifier.$set["billing.last_billing_at.#{plan_name}"] = new Date()
Account.findByIdAndUpdate account._id, modifier, (err, account) ->
logger.error err if err
async.each _.difference(account.billing.services, original_account.billing.services), (service_name, callback) ->
async.each pluggable.selectHook("service.#{service_name}.enable"), (hook, callback) ->
hook.filter account, callback
, callback
, ->
unless _.isEqual original_account.resources_limit, account.resources_limit
async.each pluggable.selectHook('account.resources_limit_changed'), (hook, callback) ->
hook.filter account, callback
, callback
else
callback()
exports.leavePlan = (req, account, plan_name, callback) ->
leaved_services = _.reject account.billing.services, (service_name) ->
for item in _.without(account.billing.plans, plan_name)
if service_name in config.plans[item].services
return true
return false
original_account = account
modifier =
$pull:
'billing.plans': plan_name
$pullAll:
'billing.services': leaved_services
$set:
'resources_limit': exports.calcResourcesLimit _.without account.billing.plans, plan_name
$unset: {}
modifier.$unset["billing.last_billing_at.#{plan_name}"] = true
Account.findByIdAndUpdate account._id, modifier, (err, account) ->
logger.error err if err
async.each leaved_services, (service_name, callback) ->
async.each pluggable.selectHook("service.#{service_name}.disable"), (hook, callback) ->
hook.filter account, callback
, callback
, ->
unless _.isEqual original_account.resources_limit, account.resources_limit
async.each pluggable.selectHook('account.resources_limit_changed'), (hook, callback) ->
hook.filter account, callback
, callback
else
callback()
exports.isForceFreeze = (account) ->
if _.isEmpty account.billing.plans
return false
@@ -180,15 +114,3 @@ exports.calcResourcesLimit = (plans) ->
limit[k] += v
return limit
exports.initPlans = ->
for name, info in config.plans
plan = new Plan info
exports.plans[name] = plan
exports.Plan = Plan = class Plan
info: null
name: null
constructor: (@info) ->
@name = @info.name

View File

@@ -0,0 +1,80 @@
{_, async} = app.libs
{logger} = app
{Node} = app.interfaces
{Account, Component} = app.models
module.exports = class ComponentType
@component_types = {}
@get: (name) ->
return @component_types[name]
constructor: (info) ->
_.extend @, info
pickPayload: (info) ->
return info
# @param callback(err)
initialize: (component, callback) ->
callback()
populateComponent: (component) ->
if component.toObject
component = component.toObject()
async.parallel
account: (callback) ->
Account.findById component.account_id, callback
coworkers: (callback) ->
async.each component.coworkers, (coworker, callback) ->
Account.findById coworker.account_id, (err, account) ->
callback _.extend coworker,
account: account
, callback
, (err, result) ->
{account, coworkers} = result
callback _.extend component,
account: account
coworkers: coworkers
physical_node: Node.nodes[component.physical_node]
# @param callback(err, component)
createComponent: (account, info, callback) ->
{name, physical_node} = info
Component.create
account_id: account._id
component_type: @name
payload: @pickPayload info
name: name
physical_node: physical_node
dependencies: {}
, (err, component) =>
@populateComponent component, (component) =>
@initialize component, (err) =>
return callback err if err
Component.markAsStatus component, 'running', callback
# @param callback(err)
destroyComponent: (component, callback) ->
Component.markAsStatus component, 'destroying', (err) =>
return callback err if err
@populateComponent component, (component) =>
@destroy component, (err) =>
return callback err if err
Component.findByIdAndRemove component._id, callback
setCoworkers: (component, updates, callback) ->
transferOwner: ->
movePhysicalNode: ->
package: ->

View File

@@ -1,14 +1,18 @@
{SSHConnection, fs, child_process, _, async} = app.libs
{config, logger} = app
clusters = exports
clusters.nodes = {}
clusters.Node = Node = class Node
module.exports = class Node
info: null
name: null
master: false
@nodes = {}
@initNodes: ->
for name, info of config.nodes
@nodes[name] = new @constructor _.extend info,
name: name
@get: (name) ->
return @nodes[name]
constructor: (@info) ->
@name = @info.name
@master = true if @info.master
@@ -157,8 +161,3 @@ clusters.Node = Node = class Node
readFileRemote: (filename, callback) ->
@runCommandRemote "sudo cat #{filename}", (err, stdout) ->
callback err, stdout
clusters.initNodes = ->
for name, info of config.nodes
clusters.nodes[name] = new Node _.extend info,
name: name

View File

@@ -0,0 +1,18 @@
module.exports = class Plan
@plans = {}
@initPlans: ->
for name, info of config.plans
@plans[name] = new @constructor _.extend info,
name: name
@get: (name) ->
return @plans[name]
constructor: (info) ->
_.extend @, info
for component_type, info of @available_components
if info.default
unless _.isArray info.default
info.default = [info.default]

View File

@@ -0,0 +1,86 @@
module.exports = class Plugin
info: null
name: null
config: null
path: null
constructor: (@info) ->
@name = info.name
@config = config.plugins[@name] ? {}
@path = path.join __dirname, '../plugin', @name
for name, payload of info.register_hooks ? {}
if payload.register_if
unless payload.register_if.apply @
continue
@registerHook name, payload
if info.initialize
info.initialize.apply @
if fs.existsSync path.join(@path, 'locale')
i18n.initPlugin @
if fs.existsSync path.join(@path, 'static')
app.express.use harp.mount "/plugin/#{@name}", path.join(@path, 'static')
registerComponent: (info) ->
component_meta = new ComponentMeta _.extend info,
plugin: @
for name, payload of info.register_hooks ? {}
if payload.register_if
unless payload.register_if.apply @
continue
@registerComponentHook name, _.extend payload,
component_meta: component_meta
pluggable.components[info.name] = component_meta
registerHook: (name, payload) ->
words = name.split '.'
last_word = words.pop()
hook_path = pluggable.selectHookPath words.join('.')
hook_path[last_word] ?= []
hook_path[last_word].push _.extend payload,
plugin: @
getTranslator: (languages) ->
return (name) =>
if _.isArray languages
t = i18n.getTranslator languages
else
t = i18n.getTranslatorByReq languages
full_name = "plugins.#{@name}.#{name}"
args = _.toArray arguments
args[0] = full_name
full_result = t.apply @, args
unless full_result == full_name
return full_result
return t.apply @, _.toArray(arguments)
render: (name, req, view_data, callback) ->
template_path = path.join __dirname, '../plugin', @name, 'view', "#{name}.jade"
locals = _.extend _.clone(req.res.locals), view_data,
account: req.account
t: @getTranslator req
jade.renderFile template_path, locals, (err, html) ->
logger.error err if err
callback html
renderTemplate: (name, view_data, callback) ->
template_path = path.join __dirname, '../plugin', @name, 'view', name
fs.readFile template_path, (err, template_file) ->
callback _.template(template_file.toString()) view_data

View File

@@ -1,10 +1,11 @@
{pluggable, utils, config, models} = app
{_, async, mongoose, mongooseUniqueValidator} = app.libs
{Financial, SecurityLog} = app.models
{Financial, SecurityLog, Component} = app.models
{Plan} = app.interfaces
process.nextTick ->
{Financial, SecurityLog} = app.models
{Financial, SecurityLog, Component} = app.models
{Plan} = app.interfaces
Token = mongoose.Schema
type:
@@ -72,9 +73,6 @@ Account = mongoose.Schema
type: Object
billing:
services:
type: Array
plans:
type: Array
@@ -247,5 +245,54 @@ Account.methods.createSecurityLog = (type, token, payload, callback) ->
payload: payload
, callback
Account.methods.getComponents = (type, callback) ->
Component.find
type: type
, (err, components) ->
callback components
Account.methods.getAvailableComponentsTypes = ->
return _.compact _.map @billing.plans, (plan_name) ->
return _.keys Plan.get(plan_name).available_components
# callback(err)
Account.methods.joinPlan = (plan, callback) ->
@billing.plans.addToSet plan.name
@save =>
async.each _.keys(plan.available_components), (component_type, callback) =>
plan_component_info = plan.available_components[component_type]
component_type = pluggable.components[component_type]
unless plan_component_info.default
return callback()
async.each plan_component_info.default, (defaultInfo, callback) =>
default_info = defaultInfo @
component_type.createComponent @,
physical_node: default_info.physical_node
name: default_info.name ? ''
payload: default_info
, callback
, callback
# callback(err)
Account.methods.leavePlan = (plan, callback) ->
@billing.plans.pull plan.name
available_component_types = @getAvailableComponentsTypes()
@save =>
@getComponents (components) ->
async.each components, (component, callback) ->
if component.component_type in available_component_types
return callback()
component_type = ComponentType.get component.component_type
component_type.destroyComponent component, callback
, callback
_.extend app.models,
Account: mongoose.model 'Account', Account

View File

@@ -27,6 +27,11 @@ Component = mongoose.Schema
enum: ['readonly', 'readwrite']
]
status:
type: String
enum: ['running', 'initializing', 'destroying']
default: 'initializing'
payload:
type: Object
@@ -38,5 +43,10 @@ Component = mongoose.Schema
type: String
enum: []
Component.statics.markAsStatus = (component, status, callback) ->
@findByIdAndUpdate component._id,
status: status
, callback
_.extend app.models,
Component: mongoose.model 'Component', Component

View File

@@ -3,9 +3,6 @@
pluggable = exports
pluggable.plugins = {}
pluggable.components = {}
pluggable.hooks =
app:
# action: function
@@ -56,97 +53,6 @@ pluggable.hooks =
# path
styles: []
Plugin = class Plugin
info: null
name: null
config: null
path: null
constructor: (@info) ->
@name = info.name
@config = config.plugins[@name] ? {}
@path = path.join __dirname, '../plugin', @name
for name, payload of info.register_hooks ? {}
if payload.register_if
unless payload.register_if.apply @
continue
@registerHook name, payload
if info.initialize
info.initialize.apply @
if fs.existsSync path.join(@path, 'locale')
i18n.initPlugin @
if fs.existsSync path.join(@path, 'static')
app.express.use harp.mount "/plugin/#{@name}", path.join(@path, 'static')
registerComponent: (info) ->
component_meta = new ComponentMeta _.extend info,
plugin: @
for path, payload of info.register_hooks ? {}
if payload.register_if and payload.register_if.apply @
@registerComponentHook path, _.extend payload,
component_meta: component_meta
pluggable.components[info.name] = component_meta
registerHook: (name, payload) ->
words = name.split '.'
last_word = words.pop()
hook_path = pluggable.selectHookPath words.join('.')
hook_path[last_word] ?= []
hook_path[last_word].push _.extend payload,
plugin: @
getTranslator: (languages) ->
return (name) =>
if _.isArray languages
t = i18n.getTranslator languages
else
t = i18n.getTranslatorByReq languages
full_name = "plugins.#{@name}.#{name}"
args = _.toArray arguments
args[0] = full_name
full_result = t.apply @, args
unless full_result == full_name
return full_result
return t.apply @, _.toArray(arguments)
render: (name, req, view_data, callback) ->
template_path = path.join __dirname, '../plugin', @name, 'view', "#{name}.jade"
locals = _.extend _.clone(req.res.locals), view_data,
account: req.account
t: @getTranslator req
jade.renderFile template_path, locals, (err, html) ->
logger.error err if err
callback html
renderTemplate: (name, view_data, callback) ->
template_path = path.join __dirname, '../plugin', @name, 'view', name
fs.readFile template_path, (err, template_file) ->
callback _.template(template_file.toString()) view_data
ComponentMeta = class ComponentMeta
info: null
name: null
constructor: (@info) ->
@name = @info.name
pluggable.selectHookPath = (name) ->
words = name.split '.'
@@ -175,7 +81,3 @@ pluggable.initPlugins = ->
throw err
exports.plugins[name] = plugin
_.extend app.classes,
Plugin: Plugin
ComponentMeta: ComponentMeta

View File

@@ -10,7 +10,7 @@ getPasswdMap = (callback) ->
callback result
describe 'clusters', ->
describe 'interface/Node', ->
clusters = null
master = null

View File

@@ -44,17 +44,25 @@ linuxPlugin.registerComponent
initialize: linux.createUser
destroy: linux.deleteUser
pickPayload: (info) ->
return {
username: info.username
}
package: ->
unpacking: ->
register_hooks:
'account.resources_limit_changed':
repeating: 'every_node'
filter: linux.setResourceLimit
'view.panel.styles':
repeating: 'once'
path: '/plugin/linux/style/panel.css'
'view.panel.widgets':
repeating: 'every'
generator: (req, callback) ->
linux.getResourceUsageByAccount req.account, (resources_usage) ->
resources_usage ?=

View File

@@ -4,34 +4,38 @@
monitor = require './monitor'
exports.createUser = (account, callback) ->
exports.createUser = (component, callback) ->
{account, physical_node} = component
async.series [
(callback) ->
child_process.exec "sudo useradd -m -s /bin/bash #{account.username}", callback
physical_node.runCommand "sudo useradd -m -s /bin/bash #{account.username}", callback
(callback) ->
child_process.exec "sudo usermod -G #{account.username} -a www-data", callback
physical_node.runCommand "sudo usermod -G #{account.username} -a www-data", callback
], (err) ->
logger.error err if err
cache.delete 'linux.getPasswdMap', callback
], callback
exports.deleteUser = (component, callback) ->
{account, physical_node} = component
exports.deleteUser = (account, callback) ->
async.series [
(callback) ->
child_process.exec "sudo pkill -u #{account.username}", ->
physical_node.runCommand "sudo pkill -u #{account.username}", (err) ->
logger.warn err if err
callback()
(callback) ->
child_process.exec "sudo userdel -rf #{account.username}", ->
physical_node.runCommand "sudo userdel -rf #{account.username}", (err) ->
logger.warn err if err
callback()
(callback) ->
child_process.exec "sudo groupdel #{account.username}", ->
physical_node.runCommand "sudo groupdel #{account.username}", (err) ->
logger.warn err if err
callback()
], ->
cache.delete 'linux.getPasswdMap', callback
], callback
exports.setResourceLimit = (account, callback) ->
unless 'linux' in account.billing.services