/*
* Copyright (c) 2013 GigaSpaces Technologies Ltd. All rights reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package models;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import javax.persistence.*;
import cloudify.widget.api.clouds.CloudProvider;
import cloudify.widget.api.clouds.CloudServer;
import cloudify.widget.api.clouds.ServerIp;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import play.cache.Cache;
import play.db.ebean.Model;
import play.libs.Json;
import utils.CollectionUtils;
import utils.StringUtils;
import com.avaje.ebean.ExpressionList;
import com.avaje.ebean.Junction;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
import com.thoughtworks.xstream.annotations.XStreamOmitField;
/**
* The ServerNode keeps all metadata of all created and available/busy servers.
*
* @author Igor Goldenberg
* @see beans.ServerBootstrapperImpl
*/
@Entity
@SuppressWarnings("serial")
@XStreamAlias("server")
public class ServerNode
extends Model
{
public static final long EXPIRED_TIME = 0L;
private static Logger logger = LoggerFactory.getLogger(ServerNode.class);
@Id
@XStreamOmitField
private Long id = null;
@XStreamAsAttribute
private String serverId;
@OneToOne
private Lead lead;
private Long busySince;
// indicates when async bootstrap was invoked
private Date asyncBootstrapStart;
// indicates when async install was invoked;
private Date asyncInstallStart;
@XStreamAsAttribute
private String publicIP; // todo : change case to Ip
@XStreamAsAttribute
private String privateIP; // todo : change case to Ip
@XStreamAsAttribute
@Lob
private String privateKey;
@XStreamAsAttribute
@Column( name = "api_key") // can't call a column "key" since it is a keyword
private String key;
private String project;
@XStreamAsAttribute
private boolean stopped = false;
@XStreamAsAttribute
private boolean remote = false;
@OneToOne( cascade = CascadeType.REMOVE, mappedBy="serverNode" )
WidgetInstance widgetInstance = null;
@Version
private long version = 0;
@Lob
public String advancedParams = null;
@Lob
public String recipeProperties = null;
@OneToOne( cascade = CascadeType.REMOVE)
public WidgetInstanceUserDetails widgetInstanceUserDetails = null;
@JsonIgnore
@OneToMany(mappedBy="serverNode", cascade = CascadeType.REMOVE)
public List<ServerNodeEvent> events = new LinkedList<ServerNodeEvent>();
@JsonIgnore
@ManyToOne( )
private Widget widget;
public static Finder<Long,ServerNode> find = new Finder<Long,ServerNode>(Long.class, ServerNode.class);
public ServerNode( ) {
}
public ServerNode( CloudServer srv )
{
this.serverId = srv.getId();
ServerIp serverIp = srv.getServerIp();
publicIP = serverIp.publicIp;
privateIP = serverIp.privateIp;
}
public String getNodeId() // guy - it is dangerous to call this getId as it looks like the getter of "long id"
{
return serverId;
}
public void setServerId( String serverId )
{
this.serverId = serverId;
}
public String getPrivateIP()
{
return privateIP;
}
public Long getId() {
return id;
}
public String getPublicIP()
{
return publicIP;
}
public String getAdvancedParams(){
return advancedParams;
}
public String getRecipeProperties(){
return recipeProperties;
}
public void setAdvancedParams(String text) {
// TODO Auto-generated method stub
advancedParams = text;
}
public void setRecipeProperties( String text ){ recipeProperties = text; }
public CloudProvider getCloudProvider(){
String typeStr = Json.parse( advancedParams ).get( "type" ).asText();
return CloudProvider.findByLabel( typeStr );
}
public boolean hasAdvancedParams(){
return !StringUtils.isEmptyOrSpaces(advancedParams);
}
public void setPublicIP(String publicIP) {
this.publicIP = publicIP;
}
public Long getTimeLeft() {
if ( remote ){
return Long.MAX_VALUE; // leave forever;
}
if (widgetInstance != null) {
if ( lead != null) {
return busySince + lead.getLeadExtraTimeout() - System.currentTimeMillis();
} else {
return Math.max(EXPIRED_TIME, busySince + widgetInstance.getWidget().getLifeExpectancy() - System.currentTimeMillis());
}
}
// server nodes can be busy but without a widget if we just took them from the pool and have yet assigned them a widget instance.
return null;
}
public Long getBusySince() {
return busySince;
}
public void setBusySince(Long busySince) {
this.busySince = busySince;
}
public boolean isExpired()
{
Long timeLeft = getTimeLeft();
return timeLeft != null && timeLeft <= 0;
}
public boolean isBusy()
{
return busySince != null;
}
public void setWidget(Widget widget) {
this.widget = widget;
}
public Widget getWidget(){
return widget;
}
static public int count()
{
return find.findRowCount();
}
static public List<ServerNode> all()
{
return find.all();
}
static public List<ServerNode> findByCriteria( QueryConf conf) {
ExpressionList<ServerNode> where = find.where();
Junction<ServerNode> disjunction = where.disjunction();
for (Criteria criteria : conf.criterias) {
ExpressionList<ServerNode> conjuction = disjunction.conjunction();
conjuction.eq("1","1"); // solves issues where criteria is actually empty.
if (criteria.busy != null) {
if (criteria.busy) {
conjuction.isNotNull("busySince");
} else {
conjuction.isNull("busySince");
}
}
if (criteria.remote != null) {
conjuction.eq("remote", criteria.remote);
}
if (criteria.stopped != null) {
conjuction.eq("stopped", criteria.stopped);
}
if ( criteria.serverIdIsNull != null ){
if ( criteria.serverIdIsNull ){
conjuction.isNull("serverId");
}else{
conjuction.isNotNull("serverId");
}
}
if ( criteria.widgetIsNull != null ){
if ( criteria.widgetIsNull ){
conjuction.isNull("widget");
}
else{
conjuction.isNotNull("widget");
}
}
if ( criteria.asyncBootstrapStartIsNull != null ){
if ( criteria.asyncBootstrapStartIsNull ){
conjuction.isNull("asyncBootstrapStart");
}else{
conjuction.isNotNull("asyncBootstrapStart");
}
}
if ( criteria.asyncInstallStartIsNull != null ){
if ( criteria.asyncInstallStartIsNull ){
conjuction.isNull("asyncInstallStart");
}else{
conjuction.isNotNull("asyncInstallStart");
}
}
if ( criteria.widgetInstanceIsNull != null ){
if ( criteria.widgetInstanceIsNull ){
conjuction.isNull("widgetInstance");
}
else{
conjuction.isNotNull("widgetInstance");
}
}
if( criteria.nodeId != null ){
conjuction.eq("serverId", criteria.nodeId );
}
if ( criteria.user != null && !criteria.user.isAdmin()){
conjuction.eq("user", criteria.user);
}
}
if ( conf.maxRows > 0 ){
where.setMaxRows( conf.maxRows );
}
return where.findList();
}
static public ServerNode getServerNode( String serverId )
{
return CollectionUtils.first(ServerNode.find.where().eq("serverId", serverId).findList());
}
public Date getAsyncBootstrapStart() {
return asyncBootstrapStart;
}
public void setAsyncBootstrapStart(Date asyncBootstrapStart) {
this.asyncBootstrapStart = asyncBootstrapStart;
}
public Date getAsyncInstallStart() {
return asyncInstallStart;
}
public void setAsyncInstallStart(Date asyncInstallStart) {
this.asyncInstallStart = asyncInstallStart;
}
public String toDebugString() {
return String.format("ServerNode{id='%s', serverId='%s', expirationTime=%d, publicIP='%s', privateIP='%s', busySince=%s}", id, serverId, getTimeLeft(), publicIP, privateIP, busySince);
}
@Override
public String toString()
{
return "ServerNode{" +
"id=" + id +
", serverId='" + serverId + '\'' +
", expirationTime=" + getTimeLeft() +
", publicIP='" + publicIP + '\'' +
", privateIP='" + privateIP + '\'' +
", busySince=" + busySince +
", advancedParams=" + advancedParams +
", remote=" + remote +
", widget=" + widget +
", widgetInstance=" + widgetInstance +
", project='" + project + '\'' +
'}';
}
public String getPrivateKey() {
return privateKey;
}
public void setPrivateKey(final String privateKey) {
this.privateKey = privateKey;
}
public String getKey() {
return key;
}
private String secretKeyToken(){
return project + "___" + key;
}
public String getSecretKey()
{
return (String) Cache.get( secretKeyToken() );
}
public void setSecretKey( String secretKey )
{
Cache.set( secretKeyToken() , secretKey );
}
public void setKey(final String key) {
this.key = key;
}
public String getProject() {
return project;
}
public void setProject(final String project) {
this.project = project;
}
public boolean isStopped() {
return stopped;
}
public void setStopped(final boolean stopped) {
this.stopped = stopped;
}
public boolean isRemote() {
return remote;
}
@Override
public void delete() {
logger.info("deleting server node [{}]", remote);
if( !remote ){
super.delete();
}else{
logger.error("deleting server node ", new RuntimeException());
// super.delete();
}
}
public void setRemote(boolean remote) {
this.remote = remote;
}
public void setWidgetInstance(WidgetInstance widgetInstance) {
this.widgetInstance = widgetInstance;
}
@JsonIgnore
public WidgetInstance getWidgetInstance()
{
return widgetInstance;
}
public ServerNodeEvent errorEvent(String message) {
return createEvent( message, ServerNodeEvent.Type.ERROR );
}
public ServerNodeEvent createEvent( String message, ServerNodeEvent.Type type ){
logger.info("adding [{}] event [{}]", type, message);
return new ServerNodeEvent()
.setServerNode( this )
.setMsg( message )
.setEventType(type);
}
public ServerNodeEvent infoEvent( String s ) {
return createEvent(s, ServerNodeEvent.Type.INFO);
}
public Lead getLead() {
return lead;
}
public void setLead(Lead lead) {
this.lead = lead;
}
public static ServerNode findByWidgetAndInstanceId(Widget widget, String instanceId) {
return find.where().eq("widget",widget).eq("id", instanceId).findUnique();
}
// guy - todo - formalize this for reuse.
public static class QueryConf {
public int maxRows = -1;
public List<Criteria> criterias = new LinkedList<Criteria>();
public QueryConf setMaxRows(int maxRows) {
this.maxRows = maxRows;
return this;
}
public Criteria criteria(){
Criteria c = new Criteria(this);
criterias.add(c);
return c;
}
}
public static class Criteria{
public Boolean remote = null;
public Boolean stopped = null;
public Boolean busy = null;
public String nodeId = null;
private QueryConf conf;
private Boolean serverIdIsNull;
private Boolean widgetIsNull;
private Boolean widgetInstanceIsNull;
private Boolean asyncBootstrapStartIsNull;
private Boolean asyncInstallStartIsNull;
private User user;
public Criteria(QueryConf conf) {
this.conf = conf;
}
public Criteria setRemote(Boolean remote) {
this.remote = remote;
return this;
}
public QueryConf done(){
return conf;
}
public Criteria setStopped(Boolean stopped) {
this.stopped = stopped;
return this;
}
public Criteria setBusy(Boolean busy) {
this.busy = busy;
return this;
}
public Criteria setAsyncBootstrapStartIsNull(Boolean asyncBootstrapStartIsNull) {
this.asyncBootstrapStartIsNull = asyncBootstrapStartIsNull;
return this;
}
public Criteria setAsyncInstallStartIsNull(Boolean asyncInstallStartIsNull) {
this.asyncInstallStartIsNull = asyncInstallStartIsNull;
return this;
}
public Criteria setNodeId(String nodeId) {
this.nodeId = nodeId;
return this;
}
public Criteria setServerIdIsNull(boolean serverIdIsNull) {
this.serverIdIsNull = serverIdIsNull;
return this;
}
public Criteria setWidgetIsNull(boolean widgetIsNull) {
this.widgetIsNull = widgetIsNull;
return this;
}
public Criteria setWidgetInstanceIsNull(boolean widgetInstanceIsNull) {
this.widgetInstanceIsNull = widgetInstanceIsNull;
return this;
}
public Criteria setUser(User user) {
this.user = user;
return this;
}
}
}