diff --git a/.gitignore b/.gitignore index 1a645d9fac8..82464765226 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ dist/ cloud-*.tar.bz2 *.log *.pyc +*.egginfo/ build.number api.log.*.gz cloud.log.*.* @@ -45,6 +46,7 @@ deps/awsapi-lib/ git-remote-https.exe.stackdump *.swp tools/devcloud/devcloudbox/.vagrant +tools/cli/cloudmonkey/marvin/ *.jar *.war *.mar diff --git a/agent/src/com/cloud/agent/Agent.java b/agent/src/com/cloud/agent/Agent.java index 4bfd0765161..84b0db62684 100755 --- a/agent/src/com/cloud/agent/Agent.java +++ b/agent/src/com/cloud/agent/Agent.java @@ -27,7 +27,6 @@ import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; - import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.SynchronousQueue; @@ -48,6 +47,7 @@ import com.cloud.agent.api.MaintainAnswer; import com.cloud.agent.api.MaintainCommand; import com.cloud.agent.api.ModifySshKeysCommand; import com.cloud.agent.api.PingCommand; +import com.cloud.agent.api.ReadyCommand; import com.cloud.agent.api.ShutdownCommand; import com.cloud.agent.api.StartupAnswer; import com.cloud.agent.api.StartupCommand; @@ -491,6 +491,10 @@ public class Agent implements HandlerFactory, IAgentControl { cancelTasks(); _reconnectAllowed = false; answer = new Answer(cmd, true, null); + } else if (cmd instanceof ReadyCommand && ((ReadyCommand)cmd).getDetails() != null) { + s_logger.debug("Not ready to connect to mgt server: " + ((ReadyCommand)cmd).getDetails()); + System.exit(1); + return; } else if (cmd instanceof MaintainCommand) { s_logger.debug("Received maintainCommand" ); cancelTasks(); @@ -513,6 +517,9 @@ public class Agent implements HandlerFactory, IAgentControl { } } else { + if (cmd instanceof ReadyCommand) { + processReadyCommand((ReadyCommand)cmd); + } _inProgress.incrementAndGet(); try { answer = _resource.executeRequest(cmd); @@ -576,6 +583,19 @@ public class Agent implements HandlerFactory, IAgentControl { setLastPingResponseTime(); } } + + + public void processReadyCommand(Command cmd) { + + final ReadyCommand ready = (ReadyCommand) cmd; + + s_logger.info("Proccess agent ready command, agent id = " + ready.getHostId()); + if (ready.getHostId() != null) { + setId(ready.getHostId()); + } + s_logger.info("Ready command is processed: agent id = " + getId()); + + } public void processOtherTask(Task task) { final Object obj = task.get(); @@ -601,6 +621,7 @@ public class Agent implements HandlerFactory, IAgentControl { } catch (final ClosedChannelException e) { s_logger.warn("Unable to send request: " + request.toString()); } + } else if (obj instanceof Request) { final Request req = (Request) obj; final Command command = req.getCommand(); diff --git a/api/src/com/cloud/agent/api/ReadyCommand.java b/api/src/com/cloud/agent/api/ReadyCommand.java index b2502964c91..0c91a2555fa 100644 --- a/api/src/com/cloud/agent/api/ReadyCommand.java +++ b/api/src/com/cloud/agent/api/ReadyCommand.java @@ -23,12 +23,18 @@ public class ReadyCommand extends Command { } private Long dcId; + private Long hostId; public ReadyCommand(Long dcId) { super(); this.dcId = dcId; } + public ReadyCommand(Long dcId, Long hostId) { + this(dcId); + this.hostId = hostId; + } + public void setDetails(String details) { _details = details; } @@ -46,4 +52,7 @@ public class ReadyCommand extends Command { return true; } + public Long getHostId() { + return hostId; + } } diff --git a/api/src/com/cloud/async/SyncQueueItem.java b/api/src/com/cloud/async/SyncQueueItem.java index f299481eb11..9f9c379a742 100644 --- a/api/src/com/cloud/async/SyncQueueItem.java +++ b/api/src/com/cloud/async/SyncQueueItem.java @@ -16,7 +16,9 @@ // under the License. package com.cloud.async; + public interface SyncQueueItem { + public final String AsyncJobContentType = "AsyncJob"; String getContentType(); diff --git a/client/WEB-INF/classes/resources/messages.properties b/client/WEB-INF/classes/resources/messages.properties index ce76a84c9b3..71f8194ee0f 100644 --- a/client/WEB-INF/classes/resources/messages.properties +++ b/client/WEB-INF/classes/resources/messages.properties @@ -30,6 +30,7 @@ label.broadcast.uri=Broadcast URI #modified labels (begin) ***************************************************************************************** +label.site.to.site.VPN=Site-to-site VPN message.zoneWizard.enable.local.storage=WARNING: If you enable local storage for this zone, you must do the following, depending on where you would like your system VMs to launch:

1. If system VMs need to be launched in primary storage, primary storage needs to be added to the zone after creation. You must also start the zone in a disabled state.

2. If system VMs need to be launched in local storage, system.vm.use.local.storage needs to be set to true before you enable the zone.


Would you like to continue? #modified labels (end) ******************************************************************************************* @@ -61,7 +62,6 @@ label.CIDR.of.destination.network=CIDR of destination network label.add.route=Add route label.add.static.route=Add static route label.remove.static.route=Remove static route -label.site.to.site.VPN=site-to-site VPN label.add.VPN.gateway=Add VPN Gateway message.add.VPN.gateway=Please confirm that you want to add a VPN Gateway label.VPN.gateway=VPN Gateway diff --git a/client/pom.xml b/client/pom.xml index c3a048604b1..056e560baff 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -40,6 +40,11 @@ cloud-plugin-user-authenticator-plaintext ${project.version} + + org.apache.cloudstack + cloud-plugin-user-authenticator-sha256salted + ${project.version} + org.apache.cloudstack cloud-plugin-network-nvp @@ -159,7 +164,7 @@ + todir="${basedir}/target/generated-webapp/WEB-INF/classes/scripts"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -208,6 +255,39 @@ + + + org.apache.maven.plugins + maven-dependency-plugin + 2.5.1 + + + copy + package + + copy + + + + + org.jasypt + jasypt + 1.9.0` + false + ${project.build.directory}/pythonlibs + + + org.jasypt + jasypt + 1.8` + false + ${project.build.directory}/pythonlibs + + + + + + diff --git a/client/tomcatconf/components.xml.in b/client/tomcatconf/components.xml.in index 2953eb781a6..5957b61c0fb 100755 --- a/client/tomcatconf/components.xml.in +++ b/client/tomcatconf/components.xml.in @@ -109,6 +109,7 @@ under the License. + diff --git a/client/tomcatconf/server-nonssl.xml b/client/tomcatconf/server-nonssl.xml.in similarity index 100% rename from client/tomcatconf/server-nonssl.xml rename to client/tomcatconf/server-nonssl.xml.in diff --git a/core/src/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java b/core/src/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java index 7e53f03e431..2dc7950b2b7 100755 --- a/core/src/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java +++ b/core/src/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java @@ -264,8 +264,35 @@ public class VirtualRoutingResource implements Manager { return new SetPortForwardingRulesAnswer(cmd, results, endResult); } + + protected Answer SetVPCStaticNatRules(SetStaticNatRulesCommand cmd) { + String routerIp = cmd.getAccessDetail(NetworkElementCommand.ROUTER_IP); + String[] results = new String[cmd.getRules().length]; + int i = 0; + boolean endResult = true; + + for (StaticNatRuleTO rule : cmd.getRules()) { + String args = rule.revoked() ? " -D" : " -A"; + args += " -l " + rule.getSrcIp(); + args += " -r " + rule.getDstIp(); + + String result = routerProxy("vpc_staticnat.sh", routerIp, args); + + if(result == null) { + results[i++] = null; + } else { + results[i++] = "Failed"; + endResult = false; + } + } + return new SetStaticNatRulesAnswer(cmd, results, endResult); + + } private Answer execute(SetStaticNatRulesCommand cmd) { + if ( cmd.getVpcId() != null ) { + return SetVPCStaticNatRules(cmd); + } String routerIp = cmd.getAccessDetail(NetworkElementCommand.ROUTER_IP); String[] results = new String[cmd.getRules().length]; int i = 0; diff --git a/debian/control b/debian/control index 44736d74511..380b2e4a78d 100644 --- a/debian/control +++ b/debian/control @@ -90,7 +90,7 @@ Description: CloudStack agent libraries Package: cloud-agent Architecture: any -Depends: openjdk-6-jre, cloud-utils (= ${source:Version}), cloud-core (= ${source:Version}), cloud-agent-deps (= ${source:Version}), cloud-python (= ${source:Version}), cloud-agent-libs (= ${source:Version}), cloud-scripts (= ${source:Version}), libvirt0, sysvinit-utils, chkconfig, qemu-kvm, libvirt-bin, uuid-runtime, rsync, grep, iproute, ebtables, vlan, liblog4j1.2-java (>= 1.2.16), libjna-java, wget, jsvc, lsb-base (>= 3.2) +Depends: openjdk-6-jre, cloud-utils (= ${source:Version}), cloud-core (= ${source:Version}), cloud-agent-deps (= ${source:Version}), cloud-python (= ${source:Version}), cloud-agent-libs (= ${source:Version}), cloud-scripts (= ${source:Version}), cloud-system-iso (= ${source:Version}), libvirt0, sysvinit-utils, chkconfig, qemu-kvm, libvirt-bin, uuid-runtime, rsync, grep, iproute, ebtables, vlan, liblog4j1.2-java (>= 1.2.16), libjna-java, wget, jsvc, lsb-base (>= 3.2) Description: CloudStack agent The CloudStack agent is in charge of managing shared computing resources in a CloudStack powered cloud. Install this package if this computer diff --git a/developer/developer-prefill.sql b/developer/developer-prefill.sql new file mode 100644 index 00000000000..8e215ba77d9 --- /dev/null +++ b/developer/developer-prefill.sql @@ -0,0 +1,52 @@ +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you 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. + +-- Add a default ROOT domain +INSERT INTO `cloud`.`domain` (id, name, parent, path, owner) VALUES + (1, 'ROOT', NULL, '/', 2); + +-- Add system and admin accounts +INSERT INTO `cloud`.`account` (id, account_name, type, domain_id, state) VALUES + (1, 'system', 1, 1, 'enabled'); + +INSERT INTO `cloud`.`account` (id, account_name, type, domain_id, state) VALUES + (2, 'admin', 1, 1, 'enabled'); + +-- Add system user +INSERT INTO `cloud`.`user` (id, username, password, account_id, firstname, + lastname, email, state, created) VALUES (1, 'system', RAND(), + '1', 'system', 'cloud', NULL, 'enabled', NOW()); + +-- Add system user with encrypted password=password +INSERT INTO `cloud`.`user` (id, username, password, account_id, firstname, + lastname, email, state, created) VALUES (2, 'admin', '5f4dcc3b5aa765d61d8327deb882cf99', + '2', 'Admin', 'User', 'admin@mailprovider.com', 'enabled', NOW()); + +-- Add configurations +INSERT INTO `cloud`.`configuration` (category, instance, component, name, value) + VALUES ('Hidden', 'DEFAULT', 'management-server', 'init', 'false'); + +INSERT INTO `cloud`.`configuration` (category, instance, component, name, value) + VALUES ('Advanced', 'DEFAULT', 'management-server', + 'integration.api.port', '8096'); + +-- Add developer configuration entry; allows management server to be run as a user other than "cloud" +INSERT INTO `cloud`.`configuration` (category, instance, component, name, value) + VALUES ('Advanced', 'DEFAULT', 'management-server', + 'developer', 'true'); + +commit; diff --git a/developer/pom.xml b/developer/pom.xml index bba914be752..61cb1108d3a 100644 --- a/developer/pom.xml +++ b/developer/pom.xml @@ -303,32 +303,16 @@ - prefill-schema + prefill-developer-schema process-test-resources execute - INSERT INTO `cloud`.`domain` (id, name, - parent, path, owner) VALUES (1, 'ROOT', NULL, '/', - 2) - - - - prefill-configuration - process-test-resources - - execute - - - INSERT INTO `cloud`.`configuration` - (category, instance, component, name, value) VALUES - ('Hidden', 'DEFAULT', 'management-server', 'init', - 'false') - INSERT INTO `cloud`.`configuration` - (category, instance, component, name, value) VALUES - ('Advanced', 'DEFAULT', 'management-server', 'integration.api.port', - '8096') + true + + ${basedir}/developer-prefill.sql + diff --git a/docs/en-US/Release_Notes.xml b/docs/en-US/Release_Notes.xml index 849dc5af911..17f9079cc01 100644 --- a/docs/en-US/Release_Notes.xml +++ b/docs/en-US/Release_Notes.xml @@ -32,7 +32,7 @@ Anyone contributing to CloudStack should be on this mailing list. You can also report bugs in CloudStack using the Apache Defect Tracking - System + System. To posts to the lists, you'll need to be subscribed. See the CloudStack Web site for instructions. @@ -92,11 +92,11 @@ Name - systemvm-vmware-3.0.5 + systemvm-vmware-3.0.0 Description - systemvm-vmware-3.0.5 + systemvm-vmware-3.0.0 URL @@ -174,6 +174,7 @@ # apt-get update # apt-get upgrade cloud-* + You will, of course, have to agree to the changes suggested by Yum or APT. If the upgrade output includes a message similar to the following, then some custom content was found in your old components.xml, and you need to merge the two @@ -559,8 +560,8 @@ VMware - Name: systemvm-vmware-3.0.5 - Description: systemvm-vmware-3.0.5 + Name: systemvm-vmware-3.0.0 + Description: systemvm-vmware-3.0.0 URL: http://download.cloud.com/templates/burbank/burbank-systemvm-08012012.ova Zone: Choose the zone where this hypervisor is used @@ -619,6 +620,7 @@ # apt-get update # apt-get upgrade cloud-* + You will, of course, have to agree to the changes suggested by Yum or APT. If you have made changes to your existing copy of the file components.xml in your diff --git a/docs/en-US/configure-package-repository.xml b/docs/en-US/configure-package-repository.xml index 7687fa392ff..9ef2307b105 100644 --- a/docs/en-US/configure-package-repository.xml +++ b/docs/en-US/configure-package-repository.xml @@ -42,8 +42,8 @@
DEB package repository - You can add a DEB package repository to your apt sources with the following commands. Please note that currently only packages for Ubuntu 12.04 LTS (precise) are being build. - Use your preferred editor and open (or create) /etc/apt/sources.list.d/cloudstack. Add the community provided repository to the file: + You can add a DEB package repository to your apt sources with the following commands. Please note that only packages for Ubuntu 12.04 LTS (precise) are being built at this time. + Use your preferred editor and open (or create) /etc/apt/sources.list.d/cloudstack.list. Add the community provided repository to the file: deb http://cloudstack.apt-get.eu/ubuntu precise 4.0 We now have to add the public key to the trusted keys. $ wget -O - http://cloudstack.apt-get.eu/release.asc|apt-key add - @@ -53,20 +53,17 @@
RPM package repository - If you're using an RPM-based system, you'll want to add the Yum repository so that you can install CloudStack with Yum. + There is a RPM package repository for &PRODUCT; so you can easily install on RHEL based platforms. + If you're using an RPM-based system, you'll want to add the Yum repository so that you can install &PRODUCT; with Yum. Yum repository information is found under /etc/yum.repos.d. You'll see several .repo files in this directory, each one denoting a specific repository. - To add the CloudStack repository, visit the downloads page for the repository information. It will look something like this: + To add the &PRODUCT; repository, create /etc/yum.repos.d/cloudstack.repo and insert the following information. [cloudstack] name=cloudstack -baseurl=http://server.url/downloads/rpm/stable/ +baseurl=http://cloudstack.apt-get.eu/rhel/4.0/ enabled=1 -gpgcheck=1 +gpgcheck=0 - Next you'll want to add the GPG key: - -$ rpm --import http://server.url/downloads/RPM-GPG-KEY.txt - Now you should be able to install CloudStack using Yum.
diff --git a/docs/en-US/log-in-root-admin.xml b/docs/en-US/log-in-root-admin.xml index c0a340a4ea9..bf499d6fe95 100644 --- a/docs/en-US/log-in-root-admin.xml +++ b/docs/en-US/log-in-root-admin.xml @@ -28,7 +28,7 @@ Open your favorite Web browser and go to this URL. Substitute the IP address of your own Management Server: http://<management-server-ip-address>:8080/client - On a fresh Management Server installation, a guided tour splash screen appears. On later visits, you’ll see a login screen where you can enter a user ID and password and proceed to your Dashboard. + After logging into a fresh Management Server installation, a guided tour splash screen appears. On later visits, you’ll be taken directly into the Dashboard. If you see the first-time splash screen, choose one of the following. diff --git a/docs/en-US/management-server-install-client.xml b/docs/en-US/management-server-install-client.xml index 7d219acc623..7e81ec735fc 100644 --- a/docs/en-US/management-server-install-client.xml +++ b/docs/en-US/management-server-install-client.xml @@ -33,8 +33,9 @@ packages will depend on everything you need to run the Management server.
Downloading vhd-util + This procedure is required only for installations where XenServer is installed on the hypervisor hosts. Before setting up the Management Server, download vhd-util from vhd-util + url="http://download.cloud.com.s3.amazonaws.com/tools/vhd-util">vhd-util. If the Management Server is RHEL or CentOS, copy vhd-util to /usr/lib64/cloud/common/scripts/vm/hypervisor/xenserver. If the Management Server is Ubuntu, copy vhd-util to @@ -49,4 +50,4 @@ Install on Ubuntu apt-get install cloud-client
- + \ No newline at end of file diff --git a/docs/en-US/management-server-install-multi-node.xml b/docs/en-US/management-server-install-multi-node.xml index 1764fa90092..e61f6230ff0 100644 --- a/docs/en-US/management-server-install-multi-node.xml +++ b/docs/en-US/management-server-install-multi-node.xml @@ -32,6 +32,7 @@ linkend="sect-source-buildrpm"/> or as appropriate.
+ This step is required only for installations where XenServer is installed on the hypervisor hosts. Download vhd-util from vhd-util If the Management Server is RHEL or CentOS, copy vhd-util to @@ -41,21 +42,23 @@ linkend="sect-source-buildrpm"/> or as Ensure that necessary services are started and set to start on boot. - # service rpcbind start -# service nfs start -# chkconfig nfs on -# chkconfig rpcbind on + # service rpcbind start +# service nfs start +# chkconfig nfs on +# chkconfig rpcbind on + - Configure the database client. Note the absence of the --deploy-as argument in this + Configure the database client. Note the absence of the --deploy-as argument in this case. (For more details about the arguments to this command, see .) - # cloud-setup-databases cloud:dbpassword@dbhost -e encryption_type -m management_server_key -k database_key + # cloud-setup-databases cloud:dbpassword@dbhost -e encryption_type -m management_server_key -k database_key + Configure the OS and start the Management Server: - # cloud-setup-management + # cloud-setup-management The Management Server on this node should now be running. @@ -66,4 +69,4 @@ linkend="sect-source-buildrpm"/> or as Load Balancing. - + \ No newline at end of file diff --git a/docs/en-US/management-server-install-overview.xml b/docs/en-US/management-server-install-overview.xml index ba4c8f667ff..5f46b0099bd 100644 --- a/docs/en-US/management-server-install-overview.xml +++ b/docs/en-US/management-server-install-overview.xml @@ -23,23 +23,21 @@ -->
- Management Server Installation Overview - This section describes installing the Management Server. There are two slightly different installation flows, depending on how many Management Server nodes will be in your cloud: - - A single Management Server node, with MySQL on the same node. - Multiple Management Server nodes, with MySQL on a node separate from the Management Servers. - - In either case, each machine must meet the system requirements described in System Requirements. - For the sake of security, be sure the public Internet can not access port 8096 or port 8250 on the Management Server. - - The procedure for installing the Management Server is: - - + Management Server Installation Overview + This section describes installing the Management Server. There are two slightly different installation flows, depending on how many Management Server nodes will be in your cloud: + + A single Management Server node, with MySQL on the same node. + Multiple Management Server nodes, with MySQL on a node separate from the Management Servers. + + In either case, each machine must meet the system requirements described in System Requirements. + For the sake of security, be sure the public Internet can not access port 8096 or port 8250 on the Management Server. + The procedure for installing the Management Server is: + Prepare the Operating System - Download and install vhd-util. + (XenServer only) Download and install vhd-util. Install the First Management Server Install and Configure the MySQL database diff --git a/docs/en-US/plugin-niciranvp-provider.xml b/docs/en-US/plugin-niciranvp-provider.xml index d81db99d9c0..80fb2273238 100644 --- a/docs/en-US/plugin-niciranvp-provider.xml +++ b/docs/en-US/plugin-niciranvp-provider.xml @@ -27,7 +27,7 @@ addNetworkServiceProvider - name = "NiciraNVP" + name = "NiciraNvp" physicalnetworkid = <the uuid of the physical network> diff --git a/docs/en-US/using-netscaler-load-balancers.xml b/docs/en-US/using-netscaler-load-balancers.xml index 7febb8248a3..c2044de527b 100644 --- a/docs/en-US/using-netscaler-load-balancers.xml +++ b/docs/en-US/using-netscaler-load-balancers.xml @@ -34,7 +34,7 @@ NetScaler ADC Type Description of Capabilities - &PRODUCT; 3.0.3 Supported Features + &PRODUCT; Supported Features diff --git a/docs/en-US/vcenter-maintenance-mode.xml b/docs/en-US/vcenter-maintenance-mode.xml index a07300eee58..fb896b2b166 100644 --- a/docs/en-US/vcenter-maintenance-mode.xml +++ b/docs/en-US/vcenter-maintenance-mode.xml @@ -35,7 +35,7 @@ First use vCenter to exit the vCenter maintenance mode. This makes the host ready for &PRODUCT; to reactivate it. Then use &PRODUCT;'s administrator UI to cancel the &PRODUCT; maintenance mode - When the host comes back online, the VMs that were migrated off of it are migrated back to it and new VMs can be added. + When the host comes back online, the VMs that were migrated off of it may be migrated back to it manually and new VMs can be added.
diff --git a/docs/en-US/verifying-source.xml b/docs/en-US/verifying-source.xml index 05b54ecb6a9..f8bd102379d 100644 --- a/docs/en-US/verifying-source.xml +++ b/docs/en-US/verifying-source.xml @@ -54,9 +54,9 @@
MD5 - In addition to the cryptographic signature, the &PRODUCT; provides a number - of cryptographic hashes to aid in assurance of validity of the downloaded - release. You can verify this hash by executing the following command: + In addition to the cryptographic signature, &PRODUCT; has an MD5 checksum + that you can use to verify the download matches the release. + You can verify this hash by executing the following command: $ gpg --print-md MD5 apache-cloudstack-4.0.0-incubating-src.tar.bz2 | diff - apache-cloudstack-4.0.0-incubating-src.tar.bz2.md5 diff --git a/packaging/centos63/cloud-ipallocator.rc b/packaging/centos63/cloud-ipallocator.rc new file mode 100755 index 00000000000..ffeffa342c8 --- /dev/null +++ b/packaging/centos63/cloud-ipallocator.rc @@ -0,0 +1,98 @@ +#!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# chkconfig: 35 99 10 +# description: Cloud Agent + +# WARNING: if this script is changed, then all other initscripts MUST BE changed to match it as well + +. /etc/rc.d/init.d/functions + +whatami=cloud-external-ipallocator + +# set environment variables + +SHORTNAME="$whatami" +PIDFILE=/var/run/"$whatami".pid +LOCKFILE=/var/lock/subsys/"$SHORTNAME" +LOGFILE=/var/log/cloud/ipallocator/ipallocator.log +PROGNAME="External IPAllocator" + +unset OPTIONS +[ -r /etc/sysconfig/"$SHORTNAME" ] && source /etc/sysconfig/"$SHORTNAME" +DAEMONIZE=/usr/bin/cloud-daemonize +PROG=/usr/bin/cloud-external-ipallocator.py +OPTIONS=8083 + +start() { + echo -n $"Starting $PROGNAME: " + if hostname --fqdn >/dev/null 2>&1 ; then + daemon --check=$SHORTNAME --pidfile=${PIDFILE} "$DAEMONIZE" \ + -n "$SHORTNAME" -p "$PIDFILE" -l "$LOGFILE" "$PROG" $OPTIONS + RETVAL=$? + echo + else + failure + echo + echo The host name does not resolve properly to an IP address. Cannot start "$PROGNAME". > /dev/stderr + RETVAL=9 + fi + [ $RETVAL = 0 ] && touch ${LOCKFILE} + return $RETVAL +} + +stop() { + echo -n $"Stopping $PROGNAME: " + killproc -p ${PIDFILE} $SHORTNAME # -d 10 $SHORTNAME + RETVAL=$? + echo + [ $RETVAL = 0 ] && rm -f ${LOCKFILE} ${PIDFILE} +} + + +# See how we were called. +case "$1" in + start) + start + ;; + stop) + stop + ;; + status) + status -p ${PIDFILE} $SHORTNAME + RETVAL=$? + ;; + restart) + stop + sleep 3 + start + ;; + condrestart) + if status -p ${PIDFILE} $SHORTNAME >&/dev/null; then + stop + sleep 3 + start + fi + ;; + *) + echo $"Usage: $whatami {start|stop|restart|condrestart|status|help}" + RETVAL=3 +esac + +exit $RETVAL + diff --git a/packaging/centos63/cloud-management.rc b/packaging/centos63/cloud-management.rc new file mode 100755 index 00000000000..48c2ab44a62 --- /dev/null +++ b/packaging/centos63/cloud-management.rc @@ -0,0 +1,107 @@ +#!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# +# cloud-management This shell script takes care of starting and stopping Tomcat +# +# chkconfig: - 80 20 +# +### BEGIN INIT INFO +# Provides: tomcat6 +# Required-Start: $network $syslog +# Required-Stop: $network $syslog +# Default-Start: +# Default-Stop: +# Description: Release implementation for Servlet 2.5 and JSP 2.1 +# Short-Description: start and stop tomcat +### END INIT INFO +# +# - originally written by Henri Gomez, Keith Irwin, and Nicolas Mailhot +# - heavily rewritten by Deepak Bhole and Jason Corley +# + +if [ -r /etc/rc.d/init.d/functions ]; then + . /etc/rc.d/init.d/functions +fi +if [ -r /lib/lsb/init-functions ]; then + . /lib/lsb/init-functions +fi + + +NAME="$(basename $0)" +stop() { + SHUTDOWN_WAIT="30" + count="0" + if [ -f /var/run/cloud-management.pid ]; then + pid=`cat /var/run/cloud-management.pid` + kill $pid &>/dev/null + until [ "$(ps --pid $pid | grep -c $pid)" -eq "0" ] || \ + [ "$count" -gt "$SHUTDOWN_WAIT" ] + do + sleep 1 + let count="${count}+1" + done + if [ "$(ps --pid $pid | grep -c $pid)" -eq "0" ]; then + log_success_msg "Stopping cloud-management:" + else + log_failure_msg "Stopping cloud-management:" + fi + else + echo "Cannot find PID file of Cloud-management" + log_failure_msg "Stopping cloud-management:" + fi +} + +set_ulimit() { + fd_limit=`ulimit -n` + if [ "$fd_limit" != "4096" ]; then + user=`whoami` + if [ $user == "root" ]; then + ulimit -n 4096 + fi + fi +} + +handle_pid_file() { + if [ "$1" -ne 0 ] ; then + echo "The pid file locates at /var/run/cloud-management.pid and lock file at /var/lock/subsys/cloud-management. + Starting cloud-management will take care of them or you can manually clean up." + fi +} + +# See how we were called. +case "$1" in + status) + status ${NAME} + RETVAL=$? + handle_pid_file $RETVAL + ;; + stop) + stop + ;; + restart) + stop + set start + set_ulimit + . /etc/rc.d/init.d/tomcat6 + ;; + *) + set_ulimit + . /etc/rc.d/init.d/tomcat6 +esac + +exit $RETVAL diff --git a/packaging/centos63/cloud-management.sysconfig b/packaging/centos63/cloud-management.sysconfig new file mode 100644 index 00000000000..15df8cdab92 --- /dev/null +++ b/packaging/centos63/cloud-management.sysconfig @@ -0,0 +1,23 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# This file is loaded in /etc/init.d/vmopsmanagement +# ATM we only do two things here: + +dummy=1 ; export TOMCAT_CFG=/etc/cloud/management/tomcat6.conf ; . /etc/cloud/management/tomcat6.conf +#-------------------------- + diff --git a/packaging/centos63/cloud.spec b/packaging/centos63/cloud.spec new file mode 100644 index 00000000000..b0f4046fef5 --- /dev/null +++ b/packaging/centos63/cloud.spec @@ -0,0 +1,278 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +%define __os_install_post %{nil} +%global debug_package %{nil} + +# DISABLE the post-percentinstall java repacking and line number stripping +# we need to find a way to just disable the java repacking and line number stripping, but not the autodeps + +Name: cloud +Summary: CloudStack IaaS Platform +#http://fedoraproject.org/wiki/PackageNamingGuidelines#Pre-Release_packages +%if "%{?_prerelease}" != "" +%define _maventag %{_ver}-SNAPSHOT +Release: %{_rel}%{dist} +%else +%define _maventag %{_ver} +Release: %{_rel}%{dist} +%endif +Version: %{_ver} +License: Apache License 2.0 +Vendor: Apache CloudStack +Packager: Apache CloudStack +Group: System Environment/Libraries +# FIXME do groups for every single one of the subpackages +Source0: %{name}-%{_maventag}.tgz +BuildRoot: %{_tmppath}/%{name}-%{_maventag}-%{release}-build + +BuildRequires: java-1.6.0-openjdk-devel +BuildRequires: tomcat6 +BuildRequires: ws-commons-util +BuildRequires: jpackage-utils +BuildRequires: gcc +BuildRequires: glibc-devel +BuildRequires: /usr/bin/mkisofs +BuildRequires: MySQL-python +#BuildRequires: maven => 3.0.0 + +%description +CloudStack is a highly-scalable elastic, open source, +intelligent IaaS cloud implementation. + +%package management-server +Summary: CloudStack management server UI +Requires: tomcat6 +Requires: java >= 1.6.0 +Requires: python +Requires: bash +Requires: bzip2 +Requires: gzip +Requires: unzip +Requires: /sbin/mount.nfs +Requires: openssh-clients +Requires: nfs-utils +Requires: wget +Requires: mysql-connector-java +Requires: ws-commons-util +Requires: jpackage-utils +Requires: sudo +Requires: /sbin/service +Requires: /sbin/chkconfig +Requires: /usr/bin/ssh-keygen +Requires: mkisofs +Requires: MySQL-python +Requires: python-paramiko +Requires: ipmitool +Requires: %{name}-setup = %{_ver} +Group: System Environment/Libraries +%description management-server +The CloudStack management server is the central point of coordination, +management, and intelligence in CloudStack. + +%package setup +Summary: CloudStack database setup scripts +Requires: java >= 1.6.0 +Requires: python +Requires: MySQL-python +Requires: %{name}-python = %{_ver} +Group: System Environment/Libraries +%description setup +The scripts and commands used to setup and configure the database + +%package python +Summary: CloudStack Python library +# FIXME nuke the archdependency +Requires: python +Group: System Environment/Libraries +%description python +The CloudStack Python library contains a few Python modules that the +CloudStack uses. + + +%prep +echo Doing CloudStack build + +%setup -q -n %{name}-%{_maventag} + +%build + +# this fixes the /usr/com bug on centos5 +%define _localstatedir /var +%define _sharedstatedir /var/lib +cp packaging/centos63/replace.properties build/replace.properties +echo VERSION=%{_maventag} >> build/replace.properties +echo PACKAGE=%{name} >> build/replace.properties +mvn package -Dsystemvm + +%install +[ ${RPM_BUILD_ROOT} != "/" ] && rm -rf ${RPM_BUILD_ROOT} +mkdir -p ${RPM_BUILD_ROOT}%{_bindir} +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}/setup +mkdir -p ${RPM_BUILD_ROOT}/usr/share/%{name}/management/ +ln -sf /usr/share/tomcat6/bin ${RPM_BUILD_ROOT}/usr/share/%{name}/management/bin +ln -sf /etc/cloud/management ${RPM_BUILD_ROOT}/usr/share/%{name}/management/conf +ln -sf /usr/share/tomcat6/lib ${RPM_BUILD_ROOT}/usr/share/%{name}/management/lib +ln -sf /var/log/cloud/management ${RPM_BUILD_ROOT}/usr/share/%{name}/management/logs +ln -sf /var/cache/cloud/management/temp ${RPM_BUILD_ROOT}/usr/share/%{name}/management/temp +ln -sf /var/cache/cloud/management/work ${RPM_BUILD_ROOT}/usr/share/%{name}/management/work +mkdir -p ${RPM_BUILD_ROOT}/usr/share/%{name}/management/webapps/client +mkdir -p ${RPM_BUILD_ROOT}/var/log/%{name}/management +mkdir -p ${RPM_BUILD_ROOT}/var/log/%{name}/agent +mkdir -p ${RPM_BUILD_ROOT}/var/log/%{name}/awsapi +mkdir -p ${RPM_BUILD_ROOT}/var/log/%{name}/ipallocator +mkdir -p ${RPM_BUILD_ROOT}/var/cache/%{name}/management/work +mkdir -p ${RPM_BUILD_ROOT}/var/cache/%{name}/management/temp +mkdir -p ${RPM_BUILD_ROOT}/var/lib/%{name}/mnt +mkdir -p ${RPM_BUILD_ROOT}/var/lib/%{name}/management +mkdir -p ${RPM_BUILD_ROOT}/etc/%{name}/management +mkdir -p ${RPM_BUILD_ROOT}/etc/%{name}/management/Catalina/localhost/client +mkdir -p ${RPM_BUILD_ROOT}/etc/rc.d/init.d +mkdir -p ${RPM_BUILD_ROOT}/etc/sysconfig +mkdir -p ${RPM_BUILD_ROOT}/etc/%{name}/management/Catalina/localhost/client + +install -D client/target/utilities/bin/* ${RPM_BUILD_ROOT}%{_bindir} +install -D console-proxy/dist/systemvm.iso ${RPM_BUILD_ROOT}/usr/share/%{name}/management/webapps/client/WEB-INF/classes/vms/systemvm.iso +install -D console-proxy/dist/systemvm.zip ${RPM_BUILD_ROOT}/usr/share/%{name}/management/webapps/client/WEB-INF/classes/vms/systemvm.zip + +cp -r client/target/utilities/scripts/db/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}/setup +cp -r client/target/cloud-client-ui-4.1.0-SNAPSHOT/* ${RPM_BUILD_ROOT}/usr/share/%{name}/management/webapps/client + +for name in db.properties log4j-cloud.xml tomcat6-nonssl.conf tomcat6-ssl.conf server-ssl.xml server-nonssl.xml \ + catalina.policy catalina.properties db-enc.properties classpath.conf tomcat-users.xml web.xml ; do + mv ${RPM_BUILD_ROOT}/usr/share/%{name}/management/webapps/client/WEB-INF/classes/$name \ + ${RPM_BUILD_ROOT}/etc/%{name}/management/$name +done +mv ${RPM_BUILD_ROOT}/usr/share/%{name}/management/webapps/client/WEB-INF/classes/context.xml \ + ${RPM_BUILD_ROOT}/etc/%{name}/management/Catalina/localhost/client + +mkdir -p ${RPM_BUILD_ROOT}/usr/lib/python2.6/site-packages/ +cp -r python/lib/cloudutils ${RPM_BUILD_ROOT}/usr/lib/python2.6/site-packages/ +cp -r cloud-cli/cloudtool ${RPM_BUILD_ROOT}/usr/lib/python2.6/site-packages/ +install python/lib/cloud_utils.py ${RPM_BUILD_ROOT}/usr/lib/python2.6/site-packages/cloud_utils.py +install cloud-cli/cloudapis/cloud.py ${RPM_BUILD_ROOT}/usr/lib/python2.6/site-packages/cloudapis.py +install python/bindir/cloud-external-ipallocator.py ${RPM_BUILD_ROOT}%{_bindir}/ +install -D client/target/pythonlibs/jasypt-1.9.0.jar ${RPM_BUILD_ROOT}%{_javadir}/jasypt-1.9.0.jar +install -D client/target/pythonlibs/jasypt-1.8.jar ${RPM_BUILD_ROOT}%{_javadir}/jasypt-1.8.jar + +install -D packaging/centos63/cloud-ipallocator.rc ${RPM_BUILD_ROOT}/etc/rc.d/init.d/%{name}-ipallocator +install -D packaging/centos63/cloud-management.rc ${RPM_BUILD_ROOT}/etc/rc.d/init.d/%{name}-management +install -D packaging/centos63/cloud-management.sysconfig ${RPM_BUILD_ROOT}/etc/sysconfig/%{name}-management + +chmod 770 ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/management/Catalina +chmod 770 ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/management/Catalina/localhost +chmod 770 ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/management/Catalina/localhost/client +chmod 770 ${RPM_BUILD_ROOT}%{_sharedstatedir}/%{name}/mnt +chmod 770 ${RPM_BUILD_ROOT}%{_sharedstatedir}/%{name}/management +chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/cache/%{name}/management/work +chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/cache/%{name}/management/temp +chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/management +chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/agent +chmod -R ugo+x ${RPM_BUILD_ROOT}/usr/share/%{name}/management/webapps/client/WEB-INF/classes/scripts +%clean + +[ ${RPM_BUILD_ROOT} != "/" ] && rm -rf ${RPM_BUILD_ROOT} + + +%preun management-server +/sbin/service %{name}-management stop || true +if [ "$1" == "0" ] ; then + /sbin/chkconfig --del %{name}-management > /dev/null 2>&1 || true + /sbin/service %{name}-management stop > /dev/null 2>&1 || true +fi + +%pre management-server +id %{name} > /dev/null 2>&1 || /usr/sbin/useradd -M -c "CloudStack unprivileged user" \ + -r -s /bin/sh -d %{_sharedstatedir}/%{name}/management %{name}|| true + +# set max file descriptors for cloud user to 4096 +sed -i /"cloud hard nofile"/d /etc/security/limits.conf +sed -i /"cloud soft nofile"/d /etc/security/limits.conf +echo "cloud hard nofile 4096" >> /etc/security/limits.conf +echo "cloud soft nofile 4096" >> /etc/security/limits.conf +rm -rf %{_localstatedir}/cache/%{name} +# user harcoded here, also hardcoded on wscript + +%post management-server +if [ "$1" == "1" ] ; then + /sbin/chkconfig --add %{name}-management > /dev/null 2>&1 || true + /sbin/chkconfig --level 345 %{name}-management on > /dev/null 2>&1 || true +fi + +if [ ! -f %{_datadir}/%{name}/management/webapps/client/WEB-INF/classes/scripts/scripts/vm/hypervisor/xenserver/vhd-util ] ; then + echo Please download vhd-util from http://download.cloud.com.s3.amazonaws.com/tools/vhd-util and put it in + echo %{_datadir}/%{name}/management/webapps/client/WEB-INF/classes/scripts/vm/hypervisor/xenserver/ +fi + +#No default permission as the permission setup is complex +%files management-server +%defattr(-,root,root,-) +%doc LICENSE +%doc NOTICE +%dir %attr(0770,root,%{name}) %{_sysconfdir}/%{name}/management/Catalina +%dir %attr(0770,root,%{name}) %{_sysconfdir}/%{name}/management/Catalina/localhost +%dir %attr(0770,root,%{name}) %{_sysconfdir}/%{name}/management/Catalina/localhost/client +%dir %{_datadir}/%{name}/management +%dir %attr(0770,root,%{name}) %{_sharedstatedir}/%{name}/mnt +%dir %attr(0770,%{name},%{name}) %{_sharedstatedir}/%{name}/management +%dir %attr(0770,root,%{name}) %{_localstatedir}/cache/%{name}/management +%dir %attr(0770,root,%{name}) %{_localstatedir}/cache/%{name}/management/work +%dir %attr(0770,root,%{name}) %{_localstatedir}/cache/%{name}/management/temp +%dir %attr(0770,root,%{name}) %{_localstatedir}/log/%{name}/management +%dir %attr(0770,root,%{name}) %{_localstatedir}/log/%{name}/agent +%config(noreplace) %{_sysconfdir}/sysconfig/%{name}-management +%config(noreplace) %{_sysconfdir}/%{name}/management +%config(noreplace) %attr(0640,root,%{name}) %{_sysconfdir}/%{name}/management/db.properties +%config(noreplace) %{_sysconfdir}/%{name}/management/log4j-%{name}.xml +%config(noreplace) %{_sysconfdir}/%{name}/management/tomcat6-nonssl.conf +%config(noreplace) %{_sysconfdir}/%{name}/management/tomcat6-ssl.conf +%attr(0755,root,root) %{_initrddir}/%{name}-management +%attr(0755,root,root) %{_bindir}/%{name}-setup-management +%attr(0755,root,root) %{_bindir}/%{name}-update-xenserver-licenses +%{_datadir}/%{name}/management/* + +%files setup +%attr(0755,root,root) %{_bindir}/%{name}-setup-databases +%attr(0755,root,root) %{_bindir}/%{name}-migrate-databases +%attr(0755,root,root) %{_bindir}/%{name}-set-guest-password +%attr(0755,root,root) %{_bindir}/%{name}-set-guest-sshkey +%attr(0755,root,root) %{_bindir}/%{name}-sysvmadm +%attr(0755,root,root) %{_bindir}/%{name}-setup-encryption +%dir %{_datadir}/%{name}/setup +%{_datadir}/%{name}/setup/*.sql +%{_datadir}/%{name}/setup/db/*.sql +%{_datadir}/%{name}/setup/*.sh +%{_datadir}/%{name}/setup/server-setup.xml +%{_javadir}/jasypt-1.9.0.jar +%{_javadir}/jasypt-1.8.jar +%doc LICENSE +%doc NOTICE + +%files python +%defattr(0644,root,root,0755) +%{_prefix}/lib*/python*/site-packages/%{name}* +%attr(0755,root,root) %{_bindir}/cloud-external-ipallocator.py +%attr(0755,root,root) %{_initrddir}/cloud-ipallocator +%dir %attr(0770,root,root) %{_localstatedir}/log/%{name}/ipallocator +%doc LICENSE +%doc NOTICE + + +%changelog +* Fri Oct 03 2012 Hugo Trippaers 4.1.0 +- new style spec file + diff --git a/packaging/centos63/package.sh b/packaging/centos63/package.sh new file mode 100644 index 00000000000..83c0e5eda4c --- /dev/null +++ b/packaging/centos63/package.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +CWD=`pwd` +RPMDIR=$CWD/../../dist/rpmbuild + + + +VERSION=`(cd ../../; mvn org.apache.maven.plugins:maven-help-plugin:2.1.1:evaluate -Dexpression=project.version) | grep -v '^\['` +if echo $VERSION | grep SNAPSHOT ; then + REALVER=`echo $VERSION | cut -d '-' -f 1` + DEFVER="-D_ver $REALVER" + DEFPRE="-D_prerelease 1" + DEFREL="-D_rel SNAPSHOT" +else + DEFVER="-D_ver $REALVER" + DEFPRE= + DEFREL= +fi + +mkdir -p $RPMDIR/SPECS +mkdir -p $RPMDIR/SOURCES/cloud-$VERSION + + +(cd ../../; tar -c --exclude .git --exclude dist . | tar -C $RPMDIR/SOURCES/cloud-$VERSION -x ) +(cd $RPMDIR/SOURCES/; tar -czf cloud-$VERSION.tgz cloud-$VERSION) + +cp cloud.spec $RPMDIR/SPECS + +(cd $RPMDIR; rpmbuild -ba SPECS/cloud.spec "-D_topdir $RPMDIR" "$DEFVER" "$DEFREL" "$DEFPRE" ) diff --git a/packaging/centos63/replace.properties b/packaging/centos63/replace.properties new file mode 100644 index 00000000000..e6efc76278c --- /dev/null +++ b/packaging/centos63/replace.properties @@ -0,0 +1,61 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +DBUSER=cloud +DBPW=cloud +DBROOTPW= +MSLOG=vmops.log +APISERVERLOG=api.log +DBHOST=localhost +MSMNTDIR=/mnt +COMPONENTS-SPEC=components-premium.xml +AWSAPILOG=awsapi.log +REMOTEHOST=localhost +AGENTCLASSPATH= +AGENTLOG=/var/log/cloud/agent/agent.log +AGENTLOGDIR=/var/log/cloud/agent/ +AGENTSYSCONFDIR=/etc/cloud/agent +APISERVERLOG=/var/log/cloud/management/apilog.log +AWSAPILOG=/var/log/cloud/awsapi/awsapi.log +BINDIR=/usr/bin +COMMONLIBDIR=/usr/share/java +CONFIGUREVARS= +DEPSCLASSPATH= +DOCDIR= +IPALOCATORLOG=/var/log/cloud/management/ipallocator.log +JAVADIR=/usr/share/java +LIBEXECDIR=/usr/libexec +LOCKDIR=/var/lock +MSCLASSPATH= +MSCONF=/etc/cloud/management +MSENVIRON=/usr/share/cloud/management +MSLOG=/var/log/cloud/management/management-server.log +MSLOGDIR=/var/log/cloud/management/ +MSMNTDIR=/var/lib/cloud/mnt +MSUSER=cloud +PIDDIR=/var/run +PLUGINJAVADIR= +PREMIUMJAVADIR= +PYTHONDIR=/usr/lib/python2.6/site-packages/ +SERVERSYSCONFDIR=/etc/sysconfig +SETUPDATADIR=/usr/share/cloud/setup +SYSCONFDIR=/etc/sysconfig +SYSTEMCLASSPATH= +SYSTEMJARS= +USAGECLASSPATH= +USAGELOG=/var/log/cloud/usage +USAGESYSCONFDIR=/etc/sysconfig diff --git a/patches/systemvm/debian/config/etc/init.d/cloud-early-config b/patches/systemvm/debian/config/etc/init.d/cloud-early-config index 7e5815682a8..258e71e24a9 100755 --- a/patches/systemvm/debian/config/etc/init.d/cloud-early-config +++ b/patches/systemvm/debian/config/etc/init.d/cloud-early-config @@ -597,6 +597,11 @@ setup_vpcrouter() { setup_vmware_extra_nics fi + if [ -f /etc/hosts ]; then + host=`hostname -s`; + grep -q $host /etc/hosts || echo "127.0.0.1 $host" >> /etc/hosts; + fi + cat > /etc/network/interfaces << EOF auto lo $1 iface lo inet loopback diff --git a/plugins/pom.xml b/plugins/pom.xml index dbdea24e17b..2009302423e 100644 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -45,6 +45,7 @@ user-authenticators/ldap user-authenticators/md5 user-authenticators/plain-text + user-authenticators/sha256salted diff --git a/plugins/user-authenticators/ldap/src/com/cloud/server/auth/LDAPUserAuthenticator.java b/plugins/user-authenticators/ldap/src/com/cloud/server/auth/LDAPUserAuthenticator.java index 7c6e52f6659..43874f61cb7 100644 --- a/plugins/user-authenticators/ldap/src/com/cloud/server/auth/LDAPUserAuthenticator.java +++ b/plugins/user-authenticators/ldap/src/com/cloud/server/auth/LDAPUserAuthenticator.java @@ -15,6 +15,8 @@ // package com.cloud.server.auth; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.util.HashMap; import java.util.Hashtable; import java.util.Map; @@ -31,6 +33,7 @@ import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import org.apache.log4j.Logger; +import org.bouncycastle.util.encoders.Base64; import com.cloud.api.ApiConstants.LDAPParams; import com.cloud.configuration.Config; @@ -40,6 +43,7 @@ import com.cloud.user.UserAccount; import com.cloud.user.dao.UserAccountDao; import com.cloud.utils.component.ComponentLocator; import com.cloud.utils.crypt.DBEncryptionUtil; +import com.cloud.utils.exception.CloudRuntimeException; @Local(value={UserAuthenticator.class}) @@ -159,4 +163,17 @@ public class LDAPUserAuthenticator extends DefaultUserAuthenticator { _userAccountDao = locator.getDao(UserAccountDao.class); return true; } + + @Override + public String encode(String password) { + // Password is not used, so set to a random string + try { + SecureRandom randomGen = SecureRandom.getInstance("SHA1PRNG"); + byte bytes[] = new byte[20]; + randomGen.nextBytes(bytes); + return Base64.encode(bytes).toString(); + } catch (NoSuchAlgorithmException e) { + throw new CloudRuntimeException("Failed to generate random password",e); + } + } } diff --git a/plugins/user-authenticators/md5/src/com/cloud/server/auth/MD5UserAuthenticator.java b/plugins/user-authenticators/md5/src/com/cloud/server/auth/MD5UserAuthenticator.java index f4b6f021f3b..b0cf0b03cd6 100644 --- a/plugins/user-authenticators/md5/src/com/cloud/server/auth/MD5UserAuthenticator.java +++ b/plugins/user-authenticators/md5/src/com/cloud/server/auth/MD5UserAuthenticator.java @@ -15,6 +15,9 @@ package com.cloud.server.auth; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.Map; import javax.ejb.Local; @@ -26,6 +29,7 @@ import com.cloud.server.ManagementServer; import com.cloud.user.UserAccount; import com.cloud.user.dao.UserAccountDao; import com.cloud.utils.component.ComponentLocator; +import com.cloud.utils.exception.CloudRuntimeException; /** * Simple UserAuthenticator that performs a MD5 hash of the password before @@ -49,31 +53,7 @@ public class MD5UserAuthenticator extends DefaultUserAuthenticator { return false; } - /** - MessageDigest md5; - try { - md5 = MessageDigest.getInstance("MD5"); - } catch (NoSuchAlgorithmException e) { - throw new CloudRuntimeException("Error", e); - } - md5.reset(); - BigInteger pwInt = new BigInteger(1, md5.digest(password.getBytes())); - - // make sure our MD5 hash value is 32 digits long... - StringBuffer sb = new StringBuffer(); - String pwStr = pwInt.toString(16); - int padding = 32 - pwStr.length(); - for (int i = 0; i < padding; i++) { - sb.append('0'); - } - sb.append(pwStr); - **/ - - // Will: The MD5Authenticator is now a straight pass-through comparison of the - // the passwords because we will not assume that the password passed in has - // already been MD5 hashed. I am keeping the above code in case this requirement changes - // or people need examples of how to MD5 hash passwords in java. - if (!user.getPassword().equals(password)) { + if (!user.getPassword().equals(encode(password))) { s_logger.debug("Password does not match"); return false; } @@ -87,4 +67,25 @@ public class MD5UserAuthenticator extends DefaultUserAuthenticator { _userAccountDao = locator.getDao(UserAccountDao.class); return true; } + + @Override + public String encode(String password) { + MessageDigest md5 = null; + try { + md5 = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new CloudRuntimeException("Unable to hash password", e); + } + + md5.reset(); + BigInteger pwInt = new BigInteger(1, md5.digest(password.getBytes())); + String pwStr = pwInt.toString(16); + int padding = 32 - pwStr.length(); + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < padding; i++) { + sb.append('0'); // make sure the MD5 password is 32 digits long + } + sb.append(pwStr); + return sb.toString(); + } } diff --git a/plugins/user-authenticators/plain-text/src/com/cloud/server/auth/PlainTextUserAuthenticator.java b/plugins/user-authenticators/plain-text/src/com/cloud/server/auth/PlainTextUserAuthenticator.java index 006daf98e9b..59e12e50048 100644 --- a/plugins/user-authenticators/plain-text/src/com/cloud/server/auth/PlainTextUserAuthenticator.java +++ b/plugins/user-authenticators/plain-text/src/com/cloud/server/auth/PlainTextUserAuthenticator.java @@ -87,4 +87,10 @@ public class PlainTextUserAuthenticator extends DefaultUserAuthenticator { _userAccountDao = locator.getDao(UserAccountDao.class); return true; } + + @Override + public String encode(String password) { + // Plaintext so no encoding at all + return password; + } } diff --git a/plugins/user-authenticators/sha256salted/pom.xml b/plugins/user-authenticators/sha256salted/pom.xml new file mode 100644 index 00000000000..3f530f76e17 --- /dev/null +++ b/plugins/user-authenticators/sha256salted/pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + cloud-plugin-user-authenticator-sha256salted + Apache CloudStack Plugin - User Authenticator SHA256 Salted + + org.apache.cloudstack + cloudstack-plugins + 4.1.0-SNAPSHOT + ../../pom.xml + + diff --git a/plugins/user-authenticators/sha256salted/src/com/cloud/server/auth/SHA256SaltedUserAuthenticator.java b/plugins/user-authenticators/sha256salted/src/com/cloud/server/auth/SHA256SaltedUserAuthenticator.java new file mode 100644 index 00000000000..26c33a5a9ec --- /dev/null +++ b/plugins/user-authenticators/sha256salted/src/com/cloud/server/auth/SHA256SaltedUserAuthenticator.java @@ -0,0 +1,122 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 com.cloud.server.auth; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Map; + +import javax.ejb.Local; +import javax.naming.ConfigurationException; + +import org.apache.log4j.Logger; +import org.bouncycastle.util.encoders.Base64; + +import com.cloud.server.ManagementServer; +import com.cloud.servlet.CloudStartupServlet; +import com.cloud.user.UserAccount; +import com.cloud.user.dao.UserAccountDao; +import com.cloud.utils.component.ComponentLocator; +import com.cloud.utils.component.Inject; +import com.cloud.utils.exception.CloudRuntimeException; + +@Local(value={UserAuthenticator.class}) +public class SHA256SaltedUserAuthenticator extends DefaultUserAuthenticator { + public static final Logger s_logger = Logger.getLogger(SHA256SaltedUserAuthenticator.class); + + @Inject + private UserAccountDao _userAccountDao; + private static int s_saltlen = 20; + + public boolean configure(String name, Map params) + throws ConfigurationException { + super.configure(name, params); + return true; + } + + /* (non-Javadoc) + * @see com.cloud.server.auth.UserAuthenticator#authenticate(java.lang.String, java.lang.String, java.lang.Long, java.util.Map) + */ + @Override + public boolean authenticate(String username, String password, + Long domainId, Map requestParameters) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Retrieving user: " + username); + } + UserAccount user = _userAccountDao.getUserAccount(username, domainId); + if (user == null) { + s_logger.debug("Unable to find user with " + username + " in domain " + domainId); + return false; + } + + try { + String storedPassword[] = user.getPassword().split(":"); + if (storedPassword.length != 2) { + s_logger.warn("The stored password for " + username + " isn't in the right format for this authenticator"); + return false; + } + byte salt[] = Base64.decode(storedPassword[0]); + String hashedPassword = encode(password, salt); + return storedPassword[1].equals(hashedPassword); + } catch (NoSuchAlgorithmException e) { + throw new CloudRuntimeException("Unable to hash password", e); + } catch (UnsupportedEncodingException e) { + throw new CloudRuntimeException("Unable to hash password", e); + } + } + + /* (non-Javadoc) + * @see com.cloud.server.auth.UserAuthenticator#encode(java.lang.String) + */ + @Override + public String encode(String password) { + // 1. Generate the salt + SecureRandom randomGen; + try { + randomGen = SecureRandom.getInstance("SHA1PRNG"); + + byte salt[] = new byte[s_saltlen]; + randomGen.nextBytes(salt); + + String saltString = new String(Base64.encode(salt)); + String hashString = encode(password, salt); + + // 3. concatenate the two and return + return saltString + ":" + hashString; + } catch (NoSuchAlgorithmException e) { + throw new CloudRuntimeException("Unable to hash password", e); + } catch (UnsupportedEncodingException e) { + throw new CloudRuntimeException("Unable to hash password", e); + } + } + + public String encode(String password, byte[] salt) throws UnsupportedEncodingException, NoSuchAlgorithmException { + byte[] passwordBytes = password.getBytes("UTF-8"); + byte[] hashSource = new byte[passwordBytes.length + s_saltlen]; + System.arraycopy(passwordBytes, 0, hashSource, 0, passwordBytes.length); + System.arraycopy(salt, 0, hashSource, passwordBytes.length, s_saltlen); + + // 2. Hash the password with the salt + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(hashSource); + byte[] digest = md.digest(); + + return new String(Base64.encode(digest)); + } +} diff --git a/plugins/user-authenticators/sha256salted/test/src/com/cloud/server/auth/test/AuthenticatorTest.java b/plugins/user-authenticators/sha256salted/test/src/com/cloud/server/auth/test/AuthenticatorTest.java new file mode 100644 index 00000000000..4e23d14fe43 --- /dev/null +++ b/plugins/user-authenticators/sha256salted/test/src/com/cloud/server/auth/test/AuthenticatorTest.java @@ -0,0 +1,63 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 src.com.cloud.server.auth.test; + +import static org.junit.Assert.*; + +import java.io.UnsupportedEncodingException; +import java.security.NoSuchAlgorithmException; +import java.util.Collections; + +import javax.naming.ConfigurationException; + +import org.bouncycastle.util.encoders.Base64; +import org.junit.Before; +import org.junit.Test; + +import com.cloud.server.auth.SHA256SaltedUserAuthenticator; + +public class AuthenticatorTest { + + @Before + public void setUp() throws Exception { + } + + @Test + public void testEncode() throws UnsupportedEncodingException, NoSuchAlgorithmException { + SHA256SaltedUserAuthenticator authenticator = + new SHA256SaltedUserAuthenticator(); + + try { + authenticator.configure("SHA256", Collections.emptyMap()); + } catch (ConfigurationException e) { + fail(e.toString()); + } + + String encodedPassword = authenticator.encode("password"); + + String storedPassword[] = encodedPassword.split(":"); + assertEquals ("hash must consist of two components", storedPassword.length, 2); + + byte salt[] = Base64.decode(storedPassword[0]); + String hashedPassword = authenticator.encode("password", salt); + + assertEquals("compare hashes", storedPassword[1], hashedPassword); + + } + +} diff --git a/pom.xml b/pom.xml index c90143c6d94..484db21e5b9 100644 --- a/pom.xml +++ b/pom.xml @@ -81,7 +81,6 @@ 1.2 1.0-20081010.060147 4.1 - true @@ -343,9 +342,10 @@ developer developer - tools/apidoc - tools/devcloud - tools/marvin + tools/apidoc + tools/devcloud + tools/marvin + tools/cli diff --git a/server/pom.xml b/server/pom.xml index 00255414ba6..06cfd7c45f3 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -91,8 +91,15 @@ org.apache.maven.plugins maven-surefire-plugin + -Xmx1024m com/cloud/upgrade/* + com/cloud/async/* + com/cloud/cluster/* + com/cloud/snapshot/* + com/cloud/storage/dao/* + com/cloud/vm/dao/* + com/cloud/vpc/* diff --git a/server/src/com/cloud/agent/manager/AgentManagerImpl.java b/server/src/com/cloud/agent/manager/AgentManagerImpl.java index 62e8acb0642..8141fdbe3e0 100755 --- a/server/src/com/cloud/agent/manager/AgentManagerImpl.java +++ b/server/src/com/cloud/agent/manager/AgentManagerImpl.java @@ -219,6 +219,7 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory, Manager { protected AgentMonitor _monitor = null; protected ExecutorService _executor; + protected ThreadPoolExecutor _connectExecutor; protected StateMachine2 _statusStateMachine = Status.getStateMachine(); @@ -274,7 +275,12 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory, Manager { registerForHostEvents(_monitor, true, true, false); _executor = new ThreadPoolExecutor(threads, threads, 60l, TimeUnit.SECONDS, new LinkedBlockingQueue(), new NamedThreadFactory("AgentTaskPool")); - + + _connectExecutor = new ThreadPoolExecutor(100, 500, 60l, TimeUnit.SECONDS, + new LinkedBlockingQueue(), new NamedThreadFactory("AgentConnectTaskPool")); + //allow core threads to time out even when there are no items in the queue + _connectExecutor.allowCoreThreadTimeOut(true); + _connection = new NioServer("AgentManager", _port, workers + 10, this); s_logger.info("Listening on " + _port + " with " + workers + " workers"); @@ -608,19 +614,19 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory, Manager { ConnectionException ce = (ConnectionException)e; if (ce.isSetupError()) { s_logger.warn("Monitor " + monitor.second().getClass().getSimpleName() + " says there is an error in the connect process for " + hostId + " due to " + e.getMessage()); - handleDisconnectWithoutInvestigation(attache, Event.AgentDisconnected); + handleDisconnectWithoutInvestigation(attache, Event.AgentDisconnected, true); throw ce; } else { s_logger.info("Monitor " + monitor.second().getClass().getSimpleName() + " says not to continue the connect process for " + hostId + " due to " + e.getMessage()); - handleDisconnectWithoutInvestigation(attache, Event.ShutdownRequested); + handleDisconnectWithoutInvestigation(attache, Event.ShutdownRequested, true); return attache; } } else if (e instanceof HypervisorVersionChangedException) { - handleDisconnectWithoutInvestigation(attache, Event.ShutdownRequested); + handleDisconnectWithoutInvestigation(attache, Event.ShutdownRequested, true); throw new CloudRuntimeException("Unable to connect " + attache.getId(), e); } else { s_logger.error("Monitor " + monitor.second().getClass().getSimpleName() + " says there is an error in the connect process for " + hostId + " due to " + e.getMessage(), e); - handleDisconnectWithoutInvestigation(attache, Event.AgentDisconnected); + handleDisconnectWithoutInvestigation(attache, Event.AgentDisconnected, true); throw new CloudRuntimeException("Unable to connect " + attache.getId(), e); } } @@ -628,13 +634,13 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory, Manager { } Long dcId = host.getDataCenterId(); - ReadyCommand ready = new ReadyCommand(dcId); + ReadyCommand ready = new ReadyCommand(dcId, host.getId()); Answer answer = easySend(hostId, ready); if (answer == null || !answer.getResult()) { // this is tricky part for secondary storage // make it as disconnected, wait for secondary storage VM to be up // return the attache instead of null, even it is disconnectede - handleDisconnectWithoutInvestigation(attache, Event.AgentDisconnected); + handleDisconnectWithoutInvestigation(attache, Event.AgentDisconnected, true); } agentStatusTransitTo(host, Event.Ready, _nodeId); @@ -828,6 +834,8 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory, Manager { } } } + + _connectExecutor.shutdownNow(); return true; } @@ -836,7 +844,7 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory, Manager { return _name; } - protected boolean handleDisconnectWithoutInvestigation(AgentAttache attache, Status.Event event) { + protected boolean handleDisconnectWithoutInvestigation(AgentAttache attache, Status.Event event, boolean transitState) { long hostId = attache.getId(); s_logger.info("Host " + hostId + " is disconnecting with event " + event); @@ -871,8 +879,11 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory, Manager { s_logger.debug("Deregistering link for " + hostId + " with state " + nextStatus); } + //remove the attache removeAgent(attache, nextStatus); - if (host != null) { + + //update the DB + if (host != null && transitState) { disconnectAgent(host, event, _nodeId); } @@ -942,7 +953,7 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory, Manager { } } - handleDisconnectWithoutInvestigation(attache, event); + handleDisconnectWithoutInvestigation(attache, event, true); host = _hostDao.findById(host.getId()); if (host.getStatus() == Status.Alert || host.getStatus() == Status.Down) { _haMgr.scheduleRestartForVmsOnHost(host, true); @@ -968,7 +979,7 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory, Manager { if (_investigate == true) { handleDisconnectWithInvestigation(_attache, _event); } else { - handleDisconnectWithoutInvestigation(_attache, _event); + handleDisconnectWithoutInvestigation(_attache, _event, true); } } catch (final Exception e) { s_logger.error("Exception caught while handling disconnect: ", e); @@ -1060,7 +1071,7 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory, Manager { AgentAttache attache = null; attache = findAttache(hostId); if (attache != null) { - handleDisconnectWithoutInvestigation(attache, Event.AgentDisconnected); + handleDisconnectWithoutInvestigation(attache, Event.AgentDisconnected, true); } return true; } else if (event == Event.ShutdownRequested) { @@ -1085,91 +1096,37 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory, Manager { return attache; } - //TODO: handle mycloud specific private AgentAttache handleConnectedAgent(final Link link, final StartupCommand[] startup, Request request) { AgentAttache attache = null; - StartupAnswer[] answers = new StartupAnswer[startup.length]; - try { - HostVO host = _resourceMgr.createHostVOForConnectedAgent(startup); + ReadyCommand ready = null; + try { + HostVO host = _resourceMgr.createHostVOForConnectedAgent(startup); if (host != null) { + ready = new ReadyCommand(host.getDataCenterId(), host.getId()); attache = createAttacheForConnect(host, link); + attache = notifyMonitorsOfConnection(attache, startup, false); } - Command cmd; - for (int i = 0; i < startup.length; i++) { - cmd = startup[i]; - if ((cmd instanceof StartupRoutingCommand) || (cmd instanceof StartupProxyCommand) || (cmd instanceof StartupSecondaryStorageCommand) || (cmd instanceof StartupStorageCommand)) { - answers[i] = new StartupAnswer(startup[i], attache.getId(), getPingInterval()); - break; - } - } - }catch (ConnectionException e) { - Command cmd; - for (int i = 0; i < startup.length; i++) { - cmd = startup[i]; - if ((cmd instanceof StartupRoutingCommand) || (cmd instanceof StartupProxyCommand) || (cmd instanceof StartupSecondaryStorageCommand) || (cmd instanceof StartupStorageCommand)) { - answers[i] = new StartupAnswer(startup[i], e.toString()); - break; - } - } - } catch (IllegalArgumentException e) { - Command cmd; - for (int i = 0; i < startup.length; i++) { - cmd = startup[i]; - if ((cmd instanceof StartupRoutingCommand) || (cmd instanceof StartupProxyCommand) || (cmd instanceof StartupSecondaryStorageCommand) || (cmd instanceof StartupStorageCommand)) { - answers[i] = new StartupAnswer(startup[i], e.toString()); - break; - } - } - } catch (CloudRuntimeException e) { - Command cmd; - for (int i = 0; i < startup.length; i++) { - cmd = startup[i]; - if ((cmd instanceof StartupRoutingCommand) || (cmd instanceof StartupProxyCommand) || (cmd instanceof StartupSecondaryStorageCommand) || (cmd instanceof StartupStorageCommand)) { - answers[i] = new StartupAnswer(startup[i], e.toString()); - break; - } - } - } - - Response response = null; - if (attache != null) { - response = new Response(request, answers[0], _nodeId, attache.getId()); - } else { - response = new Response(request, answers[0], _nodeId, -1); + } catch (Exception e) { + s_logger.debug("Failed to handle host connection: " + e.toString()); + ready = new ReadyCommand(null); + ready.setDetails(e.toString()); + } finally { + if (ready == null) { + ready = new ReadyCommand(null); + } } try { - link.send(response.toBytes()); - } catch (ClosedChannelException e) { - s_logger.debug("Failed to send startupanswer: " + e.toString()); - return null; - } - if (attache == null) { - return null; - } - - try { - attache = notifyMonitorsOfConnection(attache, startup, false); - return attache; - } catch (ConnectionException e) { - ReadyCommand ready = new ReadyCommand(null); - ready.setDetails(e.toString()); - try { + if (attache == null) { + final Request readyRequest = new Request(-1, -1, ready, false); + link.send(readyRequest.getBytes()); + } else { easySend(attache.getId(), ready); - } catch (Exception e1) { - s_logger.debug("Failed to send readycommand, due to " + e.toString()); } - return null; - } catch (CloudRuntimeException e) { - ReadyCommand ready = new ReadyCommand(null); - ready.setDetails(e.toString()); - try { - easySend(attache.getId(), ready); - } catch (Exception e1) { - s_logger.debug("Failed to send readycommand, due to " + e.toString()); - } - return null; + } catch (Exception e) { + s_logger.debug("Failed to send ready command:" + e.toString()); } + return attache; } protected class SimulateStartTask implements Runnable { @@ -1203,6 +1160,53 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory, Manager { } } } + + protected class HandleAgentConnectTask implements Runnable { + Link _link; + Command[] _cmds; + Request _request; + + HandleAgentConnectTask(Link link, final Command[] cmds, final Request request) { + _link = link; + _cmds = cmds; + _request = request; + } + + @Override + public void run() { + _request.logD("Processing the first command "); + StartupCommand[] startups = new StartupCommand[_cmds.length]; + for (int i = 0; i < _cmds.length; i++) { + startups[i] = (StartupCommand) _cmds[i]; + } + + AgentAttache attache = handleConnectedAgent(_link, startups, _request); + if (attache == null) { + s_logger.warn("Unable to create attache for agent: " + _request); + } + } + } + + protected void connectAgent(Link link, final Command[] cmds, final Request request) { + //send startupanswer to agent in the very beginning, so agent can move on without waiting for the answer for an undetermined time, if we put this logic into another thread pool. + StartupAnswer[] answers = new StartupAnswer[cmds.length]; + Command cmd; + for (int i = 0; i < cmds.length; i++) { + cmd = cmds[i]; + if ((cmd instanceof StartupRoutingCommand) || (cmd instanceof StartupProxyCommand) || (cmd instanceof StartupSecondaryStorageCommand) || (cmd instanceof StartupStorageCommand)) { + answers[i] = new StartupAnswer((StartupCommand)cmds[i], 0, getPingInterval()); + break; + } + } + Response response = null; + response = new Response(request, answers[0], _nodeId, -1); + try { + link.send(response.toBytes()); + } catch (ClosedChannelException e) { + s_logger.debug("Failed to send startupanswer: " + e.toString()); + } + _connectExecutor.execute(new HandleAgentConnectTask(link, cmds, request)); + } public class AgentHandler extends Task { public AgentHandler(Task.Type type, Link link, byte[] data) { @@ -1215,21 +1219,13 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory, Manager { Command cmd = cmds[0]; boolean logD = true; - Response response = null; if (attache == null) { - request.logD("Processing the first command "); if (!(cmd instanceof StartupCommand)) { s_logger.warn("Throwing away a request because it came through as the first command on a connect: " + request); - return; - } - - StartupCommand[] startups = new StartupCommand[cmds.length]; - for (int i = 0; i < cmds.length; i++) { - startups[i] = (StartupCommand) cmds[i]; - } - attache = handleConnectedAgent(link, startups, request); - if (attache == null) { - s_logger.warn("Unable to create attache for agent: " + request); + } else { + //submit the task for execution + request.logD("Scheduling the first command "); + connectAgent(link, cmds, request); } return; } @@ -1295,17 +1291,23 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory, Manager { if (cmd instanceof PingRoutingCommand) { boolean gatewayAccessible = ((PingRoutingCommand) cmd).isGatewayAccessible(); HostVO host = _hostDao.findById(Long.valueOf(cmdHostId)); - if (!gatewayAccessible) { - // alert that host lost connection to - // gateway (cannot ping the default route) - DataCenterVO dcVO = _dcDao.findById(host.getDataCenterId()); - HostPodVO podVO = _podDao.findById(host.getPodId()); - String hostDesc = "name: " + host.getName() + " (id:" + host.getId() + "), availability zone: " + dcVO.getName() + ", pod: " + podVO.getName(); + + if (host != null) { + if (!gatewayAccessible) { + // alert that host lost connection to + // gateway (cannot ping the default route) + DataCenterVO dcVO = _dcDao.findById(host.getDataCenterId()); + HostPodVO podVO = _podDao.findById(host.getPodId()); + String hostDesc = "name: " + host.getName() + " (id:" + host.getId() + "), availability zone: " + dcVO.getName() + ", pod: " + podVO.getName(); - _alertMgr.sendAlert(AlertManager.ALERT_TYPE_ROUTING, host.getDataCenterId(), host.getPodId(), "Host lost connection to gateway, " + hostDesc, "Host [" + hostDesc - + "] lost connection to gateway (default route) and is possibly having network connection issues."); + _alertMgr.sendAlert(AlertManager.ALERT_TYPE_ROUTING, host.getDataCenterId(), host.getPodId(), "Host lost connection to gateway, " + hostDesc, "Host [" + hostDesc + + "] lost connection to gateway (default route) and is possibly having network connection issues."); + } else { + _alertMgr.clearAlert(AlertManager.ALERT_TYPE_ROUTING, host.getDataCenterId(), host.getPodId()); + } } else { - _alertMgr.clearAlert(AlertManager.ALERT_TYPE_ROUTING, host.getDataCenterId(), host.getPodId()); + s_logger.debug("Not processing " + PingRoutingCommand.class.getSimpleName() + + " for agent id=" + cmdHostId + "; can't find the host in the DB"); } } answer = new PingAnswer((PingCommand) cmd); @@ -1328,7 +1330,7 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory, Manager { answers[i] = answer; } - response = new Response(request, answers, _nodeId, attache.getId()); + Response response = new Response(request, answers, _nodeId, attache.getId()); if (s_logger.isDebugEnabled()) { if (logD) { s_logger.debug("SeqA " + attache.getId() + "-" + response.getSequence() + ": Sending " + response); diff --git a/server/src/com/cloud/agent/manager/ClusteredAgentManagerImpl.java b/server/src/com/cloud/agent/manager/ClusteredAgentManagerImpl.java index 65ff8f0de6a..1b6dc44a1a3 100755 --- a/server/src/com/cloud/agent/manager/ClusteredAgentManagerImpl.java +++ b/server/src/com/cloud/agent/manager/ClusteredAgentManagerImpl.java @@ -270,7 +270,7 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust } @Override - protected boolean handleDisconnectWithoutInvestigation(AgentAttache attache, Status.Event event) { + protected boolean handleDisconnectWithoutInvestigation(AgentAttache attache, Status.Event event, boolean transitState) { return handleDisconnect(attache, event, false, true); } @@ -282,7 +282,7 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust protected boolean handleDisconnect(AgentAttache agent, Status.Event event, boolean investigate, boolean broadcast) { boolean res; if (!investigate) { - res = super.handleDisconnectWithoutInvestigation(agent, event); + res = super.handleDisconnectWithoutInvestigation(agent, event, true); } else { res = super.handleDisconnectWithInvestigation(agent, event); } @@ -305,7 +305,27 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust } AgentAttache attache = findAttache(hostId); if (attache != null) { - handleDisconnect(attache, Event.AgentDisconnected, false, false); + //don't process disconnect if the host is being rebalanced + if (_clusterMgr.isAgentRebalanceEnabled()) { + HostTransferMapVO transferVO = _hostTransferDao.findById(hostId); + if (transferVO != null) { + if (transferVO.getFutureOwner() == _nodeId && transferVO.getState() == HostTransferState.TransferStarted) { + s_logger.debug("Not processing " + Event.AgentDisconnected + " event for the host id=" + + hostId +" as the host is being connected to " + _nodeId); + return true; + } + } + } + + //don't process disconnect if the disconnect came for the host via delayed cluster notification, + //but the host has already reconnected to the current management server + if (!attache.forForward()) { + s_logger.debug("Not processing " + Event.AgentDisconnected + " event for the host id=" + + hostId +" as the host is directly connected to the current management server " + _nodeId); + return true; + } + + return super.handleDisconnectWithoutInvestigation(attache, Event.AgentDisconnected, false); } return true; @@ -571,6 +591,12 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust logD(data, "Cancel request received"); } agent.cancel(cancel.getSequence()); + final Long current = agent._currentSequence; + //if the request is the current request, always have to trigger sending next request in sequence, + //otherwise the agent queue will be blocked + if (req.executeInSequence() && (current != null && current == Request.getSequence(data))) { + agent.sendNext(Request.getSequence(data)); + } return; } @@ -853,7 +879,7 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust continue; } - if (transferMap.getInitialOwner() != _nodeId || attache.forForward()) { + if (transferMap.getInitialOwner() != _nodeId || attache == null || attache.forForward()) { s_logger.debug("Management server " + _nodeId + " doesn't own host id=" + hostId + " any more, skipping rebalance for the host"); iterator.remove(); _hostTransferDao.completeAgentTransfer(hostId); @@ -936,9 +962,24 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust HostVO host = _hostDao.findById(hostId); try { if (s_logger.isDebugEnabled()) { - s_logger.debug("Loading directly connected host " + host.getId() + "(" + host.getName() + ") to the management server " + _nodeId + " as a part of rebalance process"); + s_logger.debug("Disconnecting host " + host.getId() + "(" + host.getName() + " as a part of rebalance process without notification"); } - result = loadDirectlyConnectedHost(host, true); + + AgentAttache attache = findAttache(hostId); + if (attache != null) { + result = handleDisconnect(attache, Event.AgentDisconnected, false, false); + } + + if (result) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Loading directly connected host " + host.getId() + "(" + host.getName() + ") to the management server " + _nodeId + " as a part of rebalance process"); + } + result = loadDirectlyConnectedHost(host, true); + } else { + s_logger.warn("Failed to disconnect " + host.getId() + "(" + host.getName() + + " as a part of rebalance process without notification"); + } + } catch (Exception ex) { s_logger.warn("Failed to load directly connected host " + host.getId() + "(" + host.getName() + ") to the management server " + _nodeId + " as a part of rebalance process due to:", ex); result = false; @@ -1002,7 +1043,7 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust try { s_logger.debug("Management server " + _nodeId + " failed to rebalance agent " + hostId); _hostTransferDao.completeAgentTransfer(hostId); - handleDisconnectWithoutInvestigation(findAttache(hostId), Event.RebalanceFailed); + handleDisconnectWithoutInvestigation(findAttache(hostId), Event.RebalanceFailed, true); } catch (Exception ex) { s_logger.warn("Failed to reconnect host id=" + hostId + " as a part of failed rebalance task cleanup"); } @@ -1019,7 +1060,7 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust synchronized (_agents) { ClusteredDirectAgentAttache attache = (ClusteredDirectAgentAttache)_agents.get(hostId); if (attache != null && attache.getQueueSize() == 0 && attache.getNonRecurringListenersSize() == 0) { - handleDisconnectWithoutInvestigation(attache, Event.StartAgentRebalance); + handleDisconnectWithoutInvestigation(attache, Event.StartAgentRebalance, true); ClusteredAgentAttache forwardAttache = (ClusteredAgentAttache)createAttache(hostId); if (forwardAttache == null) { s_logger.warn("Unable to create a forward attache for the host " + hostId + " as a part of rebalance process"); diff --git a/server/src/com/cloud/alert/AlertManagerImpl.java b/server/src/com/cloud/alert/AlertManagerImpl.java index 2a01c3813ac..b71fc19ea4e 100755 --- a/server/src/com/cloud/alert/AlertManagerImpl.java +++ b/server/src/com/cloud/alert/AlertManagerImpl.java @@ -624,35 +624,35 @@ public class AlertManagerImpl implements AlertManager { //Cluster Level case CapacityVO.CAPACITY_TYPE_MEMORY: - msgSubject = "System Alert: Low Available Memory in cluster " +cluster.getName()+ " pod " +pod.getName()+ " of availablity zone " + dc.getName(); + msgSubject = "System Alert: Low Available Memory in cluster " +cluster.getName()+ " pod " +pod.getName()+ " of availability zone " + dc.getName(); totalStr = formatBytesToMegabytes(totalCapacity); usedStr = formatBytesToMegabytes(usedCapacity); msgContent = "System memory is low, total: " + totalStr + " MB, used: " + usedStr + " MB (" + pctStr + "%)"; alertType = ALERT_TYPE_MEMORY; break; case CapacityVO.CAPACITY_TYPE_CPU: - msgSubject = "System Alert: Low Unallocated CPU in cluster " +cluster.getName()+ " pod " +pod.getName()+ " of availablity zone " + dc.getName(); + msgSubject = "System Alert: Low Unallocated CPU in cluster " +cluster.getName()+ " pod " +pod.getName()+ " of availability zone " + dc.getName(); totalStr = _dfWhole.format(totalCapacity); usedStr = _dfWhole.format(usedCapacity); msgContent = "Unallocated CPU is low, total: " + totalStr + " Mhz, used: " + usedStr + " Mhz (" + pctStr + "%)"; alertType = ALERT_TYPE_CPU; break; case CapacityVO.CAPACITY_TYPE_STORAGE: - msgSubject = "System Alert: Low Available Storage in cluster " +cluster.getName()+ " pod " +pod.getName()+ " of availablity zone " + dc.getName(); + msgSubject = "System Alert: Low Available Storage in cluster " +cluster.getName()+ " pod " +pod.getName()+ " of availability zone " + dc.getName(); totalStr = formatBytesToMegabytes(totalCapacity); usedStr = formatBytesToMegabytes(usedCapacity); msgContent = "Available storage space is low, total: " + totalStr + " MB, used: " + usedStr + " MB (" + pctStr + "%)"; alertType = ALERT_TYPE_STORAGE; break; case CapacityVO.CAPACITY_TYPE_STORAGE_ALLOCATED: - msgSubject = "System Alert: Remaining unallocated Storage is low in cluster " +cluster.getName()+ " pod " +pod.getName()+ " of availablity zone " + dc.getName(); + msgSubject = "System Alert: Remaining unallocated Storage is low in cluster " +cluster.getName()+ " pod " +pod.getName()+ " of availability zone " + dc.getName(); totalStr = formatBytesToMegabytes(totalCapacity); usedStr = formatBytesToMegabytes(usedCapacity); msgContent = "Unallocated storage space is low, total: " + totalStr + " MB, allocated: " + usedStr + " MB (" + pctStr + "%)"; alertType = ALERT_TYPE_STORAGE_ALLOCATED; break; case CapacityVO.CAPACITY_TYPE_LOCAL_STORAGE: - msgSubject = "System Alert: Remaining unallocated Local Storage is low in cluster " +cluster.getName()+ " pod " +pod.getName()+ " of availablity zone " + dc.getName(); + msgSubject = "System Alert: Remaining unallocated Local Storage is low in cluster " +cluster.getName()+ " pod " +pod.getName()+ " of availability zone " + dc.getName(); totalStr = formatBytesToMegabytes(totalCapacity); usedStr = formatBytesToMegabytes(usedCapacity); msgContent = "Unallocated storage space is low, total: " + totalStr + " MB, allocated: " + usedStr + " MB (" + pctStr + "%)"; @@ -661,7 +661,7 @@ public class AlertManagerImpl implements AlertManager { //Pod Level case CapacityVO.CAPACITY_TYPE_PRIVATE_IP: - msgSubject = "System Alert: Number of unallocated private IPs is low in pod " +pod.getName()+ " of availablity zone " + dc.getName(); + msgSubject = "System Alert: Number of unallocated private IPs is low in pod " +pod.getName()+ " of availability zone " + dc.getName(); totalStr = Double.toString(totalCapacity); usedStr = Double.toString(usedCapacity); msgContent = "Number of unallocated private IPs is low, total: " + totalStr + ", allocated: " + usedStr + " (" + pctStr + "%)"; @@ -670,28 +670,28 @@ public class AlertManagerImpl implements AlertManager { //Zone Level case CapacityVO.CAPACITY_TYPE_SECONDARY_STORAGE: - msgSubject = "System Alert: Low Available Secondary Storage in availablity zone " + dc.getName(); + msgSubject = "System Alert: Low Available Secondary Storage in availability zone " + dc.getName(); totalStr = formatBytesToMegabytes(totalCapacity); usedStr = formatBytesToMegabytes(usedCapacity); msgContent = "Available secondary storage space is low, total: " + totalStr + " MB, used: " + usedStr + " MB (" + pctStr + "%)"; alertType = ALERT_TYPE_SECONDARY_STORAGE; break; case CapacityVO.CAPACITY_TYPE_VIRTUAL_NETWORK_PUBLIC_IP: - msgSubject = "System Alert: Number of unallocated virtual network public IPs is low in availablity zone " + dc.getName(); + msgSubject = "System Alert: Number of unallocated virtual network public IPs is low in availability zone " + dc.getName(); totalStr = Double.toString(totalCapacity); usedStr = Double.toString(usedCapacity); msgContent = "Number of unallocated public IPs is low, total: " + totalStr + ", allocated: " + usedStr + " (" + pctStr + "%)"; alertType = ALERT_TYPE_VIRTUAL_NETWORK_PUBLIC_IP; break; case CapacityVO.CAPACITY_TYPE_DIRECT_ATTACHED_PUBLIC_IP: - msgSubject = "System Alert: Number of unallocated direct attached public IPs is low in availablity zone " + dc.getName(); + msgSubject = "System Alert: Number of unallocated shared network IPs is low in availability zone " + dc.getName(); totalStr = Double.toString(totalCapacity); usedStr = Double.toString(usedCapacity); - msgContent = "Number of unallocated direct attached public IPs is low, total: " + totalStr + ", allocated: " + usedStr + " (" + pctStr + "%)"; + msgContent = "Number of unallocated shared network IPs is low, total: " + totalStr + ", allocated: " + usedStr + " (" + pctStr + "%)"; alertType = ALERT_TYPE_DIRECT_ATTACHED_PUBLIC_IP; break; case CapacityVO.CAPACITY_TYPE_VLAN: - msgSubject = "System Alert: Number of unallocated VLANs is low in availablity zone " + dc.getName(); + msgSubject = "System Alert: Number of unallocated VLANs is low in availability zone " + dc.getName(); totalStr = Double.toString(totalCapacity); usedStr = Double.toString(usedCapacity); msgContent = "Number of unallocated VLANs is low, total: " + totalStr + ", allocated: " + usedStr + " (" + pctStr + "%)"; diff --git a/server/src/com/cloud/api/response/ApiResponseSerializer.java b/server/src/com/cloud/api/response/ApiResponseSerializer.java index 1429d14ed58..bdbf685731e 100644 --- a/server/src/com/cloud/api/response/ApiResponseSerializer.java +++ b/server/src/com/cloud/api/response/ApiResponseSerializer.java @@ -33,8 +33,8 @@ import com.cloud.api.ApiDBUtils; import com.cloud.api.ApiResponseGsonHelper; import com.cloud.api.ApiServer; import com.cloud.api.BaseCmd; -import com.cloud.utils.IdentityProxy; import com.cloud.api.ResponseObject; +import com.cloud.utils.IdentityProxy; import com.cloud.utils.encoding.URLEncoder; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.uuididentity.dao.IdentityDao; @@ -70,7 +70,7 @@ public class ApiResponseSerializer { StringBuilder sb = new StringBuilder(); - sb.append("{ \"" + result.getResponseName() + "\" : "); + sb.append("{ \"").append(result.getResponseName()).append("\" : "); if (result instanceof ListResponse) { List responses = ((ListResponse) result).getResponses(); if ((responses != null) && !responses.isEmpty()) { @@ -80,19 +80,20 @@ public class ApiResponseSerializer { jsonStr = unescape(jsonStr); if (count != null && count != 0) { - sb.append("{ \"" + ApiConstants.COUNT + "\":" + count + " ,\"" + responses.get(0).getObjectName() + "\" : [ " + jsonStr); + sb.append("{ \"").append(ApiConstants.COUNT).append("\":").append(count).append(" ,\""). + append(responses.get(0).getObjectName()).append("\" : [ ").append(jsonStr); } for (int i = 1; i < ((ListResponse) result).getResponses().size(); i++) { jsonStr = gson.toJson(responses.get(i)); jsonStr = unescape(jsonStr); - sb.append(", " + jsonStr); + sb.append(", ").append(jsonStr); } sb.append(" ] }"); } else { sb.append("{ }"); } } else if (result instanceof SuccessResponse) { - sb.append("{ \"success\" : \"" + ((SuccessResponse) result).getSuccess() + "\"} "); + sb.append("{ \"success\" : \"").append(((SuccessResponse) result).getSuccess()).append("\"} "); } else if (result instanceof ExceptionResponse) { String jsonErrorText = gson.toJson((ExceptionResponse) result); jsonErrorText = unescape(jsonErrorText); @@ -104,7 +105,7 @@ public class ApiResponseSerializer { if (result instanceof AsyncJobResponse || result instanceof CreateCmdResponse) { sb.append(jsonStr); } else { - sb.append(" { \"" + result.getObjectName() + "\" : " + jsonStr + " } "); + sb.append(" { \"").append(result.getObjectName()).append("\" : ").append(jsonStr).append(" } "); } } else { sb.append("{ }"); @@ -119,13 +120,14 @@ public class ApiResponseSerializer { private static String toXMLSerializedString(ResponseObject result) { StringBuilder sb = new StringBuilder(); sb.append(""); - sb.append("<" + result.getResponseName() + " cloud-stack-version=\"" + ApiDBUtils.getVersion() + "\">"); + sb.append("<").append(result.getResponseName()).append(" cloud-stack-version=\"").append(ApiDBUtils.getVersion()).append("\">"); if (result instanceof ListResponse) { Integer count = ((ListResponse) result).getCount(); if (count != null && count != 0) { - sb.append("<" + ApiConstants.COUNT + ">" + ((ListResponse) result).getCount() + ""); + sb.append("<").append(ApiConstants.COUNT).append(">").append(((ListResponse) result).getCount()). + append(""); } List responses = ((ListResponse) result).getResponses(); if ((responses != null) && !responses.isEmpty()) { @@ -141,17 +143,17 @@ public class ApiResponseSerializer { } } - sb.append(""); + sb.append(""); return sb.toString(); } private static void serializeResponseObjXML(StringBuilder sb, ResponseObject obj) { if (!(obj instanceof SuccessResponse) && !(obj instanceof ExceptionResponse)) { - sb.append("<" + obj.getObjectName() + ">"); + sb.append("<").append(obj.getObjectName()).append(">"); } serializeResponseObjFieldsXML(sb, obj); if (!(obj instanceof SuccessResponse) && !(obj instanceof ExceptionResponse)) { - sb.append(""); + sb.append(""); } } @@ -221,24 +223,24 @@ public class ApiResponseSerializer { if(id != null && !id.isEmpty()) { // If this is the first IdentityProxy field encountered, put in a uuidList tag. if (!usedUuidList) { - sb.append("<" + serializedName.value() + ">"); + sb.append("<").append(serializedName.value()).append(">"); usedUuidList = true; } - sb.append("<" + "uuid" + ">" + id + ""); + sb.append("").append(id).append(""); } // Append the new idFieldName property also. String idFieldName = idProxy.getidFieldName(); if (idFieldName != null) { - sb.append("<" + "uuidProperty" + ">" + idFieldName + ""); + sb.append("").append(idFieldName).append(""); } } } if (usedUuidList) { // close the uuidList. - sb.append(""); + sb.append(""); } } else if (fieldValue instanceof Date) { - sb.append("<" + serializedName.value() + ">" + BaseCmd.getDateString((Date) fieldValue) + ""); + sb.append("<").append(">").append(BaseCmd.getDateString((Date) fieldValue)).append(""); } else if (fieldValue instanceof IdentityProxy) { IdentityProxy idProxy = (IdentityProxy)fieldValue; String id = (idProxy.getValue() != null ? String.valueOf(idProxy.getValue()) : ""); @@ -251,14 +253,14 @@ public class ApiResponseSerializer { } } if(id != null && !id.isEmpty()) - sb.append("<" + serializedName.value() + ">" + id + ""); + sb.append("<").append(serializedName.value()).append(">").append(id).append(""); } else { String resultString = escapeSpecialXmlChars(fieldValue.toString()); if (!(obj instanceof ExceptionResponse)) { resultString = encodeParam(resultString); } - sb.append("<" + serializedName.value() + ">" + resultString + ""); + sb.append("<").append(serializedName.value()).append(">").append(resultString).append(""); } } } diff --git a/server/src/com/cloud/async/AsyncJobManagerImpl.java b/server/src/com/cloud/async/AsyncJobManagerImpl.java index 6cf95feb977..5fb5105857c 100644 --- a/server/src/com/cloud/async/AsyncJobManagerImpl.java +++ b/server/src/com/cloud/async/AsyncJobManagerImpl.java @@ -270,7 +270,7 @@ public class AsyncJobManagerImpl implements AsyncJobManager, ClusterManagerListe Random random = new Random(); for(int i = 0; i < 5; i++) { - queue = _queueMgr.queue(syncObjType, syncObjId, "AsyncJob", job.getId(), queueSizeLimit); + queue = _queueMgr.queue(syncObjType, syncObjId, SyncQueueItem.AsyncJobContentType, job.getId(), queueSizeLimit); if(queue != null) { break; } @@ -598,60 +598,73 @@ public class AsyncJobManagerImpl implements AsyncJobManager, ClusterManagerListe return new Runnable() { @Override public void run() { - GlobalLock scanLock = GlobalLock.getInternLock("AsyncJobManagerGC"); - try { - if(scanLock.lock(ACQUIRE_GLOBAL_LOCK_TIMEOUT_FOR_COOPERATION)) { - try { - reallyRun(); - } finally { - scanLock.unlock(); - } - } - } finally { - scanLock.releaseRef(); - } - } - - private void reallyRun() { - try { - s_logger.trace("Begin cleanup expired async-jobs"); - - Date cutTime = new Date(DateUtil.currentGMTTime().getTime() - _jobExpireSeconds*1000); - - // limit to 100 jobs per turn, this gives cleanup throughput as 600 jobs per minute - // hopefully this will be fast enough to balance potential growth of job table - List l = _jobDao.getExpiredJobs(cutTime, 100); - if(l != null && l.size() > 0) { - for(AsyncJobVO job : l) { - _jobDao.expunge(job.getId()); - } - } - - // forcely cancel blocking queue items if they've been staying there for too long - List blockItems = _queueMgr.getBlockedQueueItems(_jobCancelThresholdSeconds*1000, false); - if(blockItems != null && blockItems.size() > 0) { - for(SyncQueueItemVO item : blockItems) { - if(item.getContentType().equalsIgnoreCase("AsyncJob")) { - completeAsyncJob(item.getContentId(), AsyncJobResult.STATUS_FAILED, 0, getResetResultResponse("Job is cancelled as it has been blocking others for too long")); + GlobalLock scanLock = GlobalLock.getInternLock("AsyncJobManagerGC"); + try { + if(scanLock.lock(ACQUIRE_GLOBAL_LOCK_TIMEOUT_FOR_COOPERATION)) { + try { + reallyRun(); + } finally { + scanLock.unlock(); + } + } + } finally { + scanLock.releaseRef(); + } + } + + public void reallyRun() { + try { + s_logger.trace("Begin cleanup expired async-jobs"); + + Date cutTime = new Date(DateUtil.currentGMTTime().getTime() - _jobExpireSeconds*1000); + + // limit to 100 jobs per turn, this gives cleanup throughput as 600 jobs per minute + // hopefully this will be fast enough to balance potential growth of job table + List l = _jobDao.getExpiredJobs(cutTime, 100); + if(l != null && l.size() > 0) { + for(AsyncJobVO job : l) { + expungeAsyncJob(job); + } + } + + // forcefully cancel blocking queue items if they've been staying there for too long + List blockItems = _queueMgr.getBlockedQueueItems(_jobCancelThresholdSeconds*1000, false); + if(blockItems != null && blockItems.size() > 0) { + for(SyncQueueItemVO item : blockItems) { + if(item.getContentType().equalsIgnoreCase(SyncQueueItem.AsyncJobContentType)) { + completeAsyncJob(item.getContentId(), AsyncJobResult.STATUS_FAILED, 0, + getResetResultResponse("Job is cancelled as it has been blocking others for too long")); } - - // purge the item and resume queue processing - _queueMgr.purgeItem(item.getId()); - } - } - - s_logger.trace("End cleanup expired async-jobs"); - } catch(Throwable e) { - s_logger.error("Unexpected exception when trying to execute queue item, ", e); - } finally { - StackMaid.current().exitCleanup(); - } - } - }; - } - - private long getMsid() { - if(_clusterMgr != null) { + + // purge the item and resume queue processing + _queueMgr.purgeItem(item.getId()); + } + } + + s_logger.trace("End cleanup expired async-jobs"); + } catch(Throwable e) { + s_logger.error("Unexpected exception when trying to execute queue item, ", e); + } finally { + StackMaid.current().exitCleanup(); + } + } + + + }; + } + + @DB + protected void expungeAsyncJob(AsyncJobVO job) { + Transaction txn = Transaction.currentTxn(); + txn.start(); + _jobDao.expunge(job.getId()); + //purge corresponding sync queue item + _queueMgr.purgeAsyncJobQueueItemId(job.getId()); + txn.commit(); + } + + private long getMsid() { + if(_clusterMgr != null) { return _clusterMgr.getManagementNodeId(); } @@ -666,7 +679,7 @@ public class AsyncJobManagerImpl implements AsyncJobManager, ClusterManagerListe } String contentType = item.getContentType(); - if(contentType != null && contentType.equals("AsyncJob")) { + if(contentType != null && contentType.equalsIgnoreCase(SyncQueueItem.AsyncJobContentType)) { Long jobId = item.getContentId(); if(jobId != null) { s_logger.warn("Mark job as failed as its correspoding queue-item has been discarded. job id: " + jobId); diff --git a/server/src/com/cloud/async/SyncQueueManager.java b/server/src/com/cloud/async/SyncQueueManager.java index b605f1b8670..a7032daaa47 100644 --- a/server/src/com/cloud/async/SyncQueueManager.java +++ b/server/src/com/cloud/async/SyncQueueManager.java @@ -30,4 +30,6 @@ public interface SyncQueueManager extends Manager { public List getActiveQueueItems(Long msid, boolean exclusive); public List getBlockedQueueItems(long thresholdMs, boolean exclusive); + + void purgeAsyncJobQueueItemId(long asyncJobId); } diff --git a/server/src/com/cloud/async/SyncQueueManagerImpl.java b/server/src/com/cloud/async/SyncQueueManagerImpl.java index c3f49557b00..4d1506523f6 100644 --- a/server/src/com/cloud/async/SyncQueueManagerImpl.java +++ b/server/src/com/cloud/async/SyncQueueManagerImpl.java @@ -185,13 +185,16 @@ public class SyncQueueManagerImpl implements SyncQueueManager { if(itemVO != null) { SyncQueueVO queueVO = _syncQueueDao.lockRow(itemVO.getQueueId(), true); - _syncQueueItemDao.expunge(itemVO.getId()); - - queueVO.setLastUpdated(DateUtil.currentGMTTime()); - //decrement the count - assert (queueVO.getQueueSize() > 0) : "Count reduce happens when it's already <= 0!"; - queueVO.setQueueSize(queueVO.getQueueSize() - 1); - _syncQueueDao.update(queueVO.getId(), queueVO); + _syncQueueItemDao.expunge(itemVO.getId()); + + //if item is active, reset queue information + if (itemVO.getLastProcessMsid() != null) { + queueVO.setLastUpdated(DateUtil.currentGMTTime()); + //decrement the count + assert (queueVO.getQueueSize() > 0) : "Count reduce happens when it's already <= 0!"; + queueVO.setQueueSize(queueVO.getQueueSize() - 1); + _syncQueueDao.update(queueVO.getId(), queueVO); + } } txt.commit(); } catch(Exception e) { @@ -273,5 +276,13 @@ public class SyncQueueManagerImpl implements SyncQueueManager { private boolean queueReadyToProcess(SyncQueueVO queueVO) { return queueVO.getQueueSize() < queueVO.getQueueSizeLimit(); + } + + @Override + public void purgeAsyncJobQueueItemId(long asyncJobId) { + Long itemId = _syncQueueItemDao.getQueueItemIdByContentIdAndType(asyncJobId, SyncQueueItem.AsyncJobContentType); + if (itemId != null) { + purgeItem(itemId); + } } -} \ No newline at end of file +} diff --git a/server/src/com/cloud/async/dao/SyncQueueItemDao.java b/server/src/com/cloud/async/dao/SyncQueueItemDao.java index cd9df2f4682..6b9da8b63ea 100644 --- a/server/src/com/cloud/async/dao/SyncQueueItemDao.java +++ b/server/src/com/cloud/async/dao/SyncQueueItemDao.java @@ -26,4 +26,5 @@ public interface SyncQueueItemDao extends GenericDao { public List getNextQueueItems(int maxItems); public List getActiveQueueItems(Long msid, boolean exclusive); public List getBlockedQueueItems(long thresholdMs, boolean exclusive); -} + public Long getQueueItemIdByContentIdAndType(long contentId, String contentType); +} diff --git a/server/src/com/cloud/async/dao/SyncQueueItemDaoImpl.java b/server/src/com/cloud/async/dao/SyncQueueItemDaoImpl.java index ce212981d50..5e757563bff 100644 --- a/server/src/com/cloud/async/dao/SyncQueueItemDaoImpl.java +++ b/server/src/com/cloud/async/dao/SyncQueueItemDaoImpl.java @@ -33,13 +33,25 @@ import com.cloud.async.SyncQueueItemVO; import com.cloud.utils.DateUtil; import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.GenericSearchBuilder; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.SearchCriteria.Op; import com.cloud.utils.db.Transaction; @Local(value = { SyncQueueItemDao.class }) public class SyncQueueItemDaoImpl extends GenericDaoBase implements SyncQueueItemDao { private static final Logger s_logger = Logger.getLogger(SyncQueueItemDaoImpl.class); + final GenericSearchBuilder queueIdSearch; + + protected SyncQueueItemDaoImpl() { + super(); + queueIdSearch = createSearchBuilder(Long.class); + queueIdSearch.and("contentId", queueIdSearch.entity().getContentId(), Op.EQ); + queueIdSearch.and("contentType", queueIdSearch.entity().getContentType(), Op.EQ); + queueIdSearch.selectField(queueIdSearch.entity().getId()); + queueIdSearch.done(); + } @Override @@ -132,4 +144,15 @@ public class SyncQueueItemDaoImpl extends GenericDaoBase return lockRows(sc, null, true); return listBy(sc, null); } + + + @Override + public Long getQueueItemIdByContentIdAndType(long contentId, String contentType) { + SearchCriteria sc = queueIdSearch.create(); + sc.setParameters("contentId", contentId); + sc.setParameters("contentType", contentType); + List id = customSearch(sc, null); + + return id.size() == 0 ? null : id.get(0); + } } diff --git a/server/src/com/cloud/network/NetworkManagerImpl.java b/server/src/com/cloud/network/NetworkManagerImpl.java index 398ce3acf0f..7a2ba35052d 100755 --- a/server/src/com/cloud/network/NetworkManagerImpl.java +++ b/server/src/com/cloud/network/NetworkManagerImpl.java @@ -2105,7 +2105,8 @@ public class NetworkManagerImpl implements NetworkManager, NetworkService, Manag List nics = _nicDao.listByVmId(vmProfile.getId()); // we have to implement default nics first - to ensure that default network elements start up first in multiple - // nics case)(need for setting DNS on Dhcp to domR's Ip4 address) + //nics case + // (need for setting DNS on Dhcp to domR's Ip4 address) Collections.sort(nics, new Comparator() { @Override @@ -2204,6 +2205,7 @@ public class NetworkManagerImpl implements NetworkManager, NetworkService, Manag } @Override + @DB public void release(VirtualMachineProfile vmProfile, boolean forced) throws ConcurrentOperationException, ResourceUnavailableException { List nics = _nicDao.listByVmId(vmProfile.getId()); @@ -2211,19 +2213,32 @@ public class NetworkManagerImpl implements NetworkManager, NetworkService, Manag releaseNic(vmProfile, nic); } } + + @Override + @DB public void releaseNic(VirtualMachineProfile vmProfile, Nic nic) throws ConcurrentOperationException, ResourceUnavailableException { NicVO nicVO = _nicDao.findById(nic.getId()); releaseNic(vmProfile, nicVO); } - - protected void releaseNic(VirtualMachineProfile vmProfile, NicVO nic) + @DB + protected void releaseNic(VirtualMachineProfile vmProfile, NicVO nicVO) throws ConcurrentOperationException, ResourceUnavailableException { - NetworkVO network = _networksDao.findById(nic.getNetworkId()); - if (nic.getState() == Nic.State.Reserved || nic.getState() == Nic.State.Reserving) { - Nic.State originalState = nic.getState(); + //lock the nic + Transaction txn = Transaction.currentTxn(); + txn.start(); + + NicVO nic = _nicDao.lockRow(nicVO.getId(), true); + if (nic == null) { + throw new ConcurrentOperationException("Unable to acquire lock on nic " + nic); + } + + Nic.State originalState = nic.getState(); + NetworkVO network = _networksDao.findById(nicVO.getNetworkId()); + + if (originalState == Nic.State.Reserved || originalState == Nic.State.Reserving) { if (nic.getReservationStrategy() == Nic.ReservationStrategy.Start) { NetworkGuru guru = _networkGurus.get(network.getGuruName()); nic.setState(Nic.State.Releasing); @@ -2239,6 +2254,9 @@ public class NetworkManagerImpl implements NetworkManager, NetworkService, Manag _nicDao.update(nic.getId(), nic); } } + //commit the transaction before proceeding releasing nic profile on the network elements + txn.commit(); + // Perform release on network elements for (NetworkElement element : _networkElements) { if (s_logger.isDebugEnabled()) { @@ -2252,6 +2270,7 @@ public class NetworkManagerImpl implements NetworkManager, NetworkService, Manag } else { nic.setState(Nic.State.Allocated); updateNic(nic, network.getId(), -1); + txn.commit(); } } } diff --git a/server/src/com/cloud/network/router/VpcVirtualNetworkApplianceManagerImpl.java b/server/src/com/cloud/network/router/VpcVirtualNetworkApplianceManagerImpl.java index 3139998c5c3..1fd710dc23d 100644 --- a/server/src/com/cloud/network/router/VpcVirtualNetworkApplianceManagerImpl.java +++ b/server/src/com/cloud/network/router/VpcVirtualNetworkApplianceManagerImpl.java @@ -301,9 +301,7 @@ public class VpcVirtualNetworkApplianceManagerImpl extends VirtualNetworkApplian result = result && _itMgr.removeVmFromNetwork(router, network, null); if (result) { - if (result) { - _routerDao.removeRouterFromGuestNetwork(router.getId(), network.getId()); - } + _routerDao.removeRouterFromGuestNetwork(router.getId(), network.getId()); } return result; } diff --git a/server/src/com/cloud/server/ConfigurationServerImpl.java b/server/src/com/cloud/server/ConfigurationServerImpl.java index 3368c9ba116..420b1722e0f 100755 --- a/server/src/com/cloud/server/ConfigurationServerImpl.java +++ b/server/src/com/cloud/server/ConfigurationServerImpl.java @@ -32,6 +32,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -85,6 +86,7 @@ import com.cloud.offerings.NetworkOfferingServiceMapVO; import com.cloud.offerings.NetworkOfferingVO; import com.cloud.offerings.dao.NetworkOfferingDao; import com.cloud.offerings.dao.NetworkOfferingServiceMapDao; +import com.cloud.server.auth.UserAuthenticator; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.DiskOfferingVO; @@ -96,6 +98,7 @@ import com.cloud.user.User; import com.cloud.user.dao.AccountDao; import com.cloud.utils.PasswordGenerator; import com.cloud.utils.PropertiesUtil; +import com.cloud.utils.component.Adapters; import com.cloud.utils.component.ComponentLocator; import com.cloud.utils.crypt.DBEncryptionUtil; import com.cloud.utils.db.DB; @@ -342,30 +345,13 @@ public class ConfigurationServerImpl implements ConfigurationServer { } catch (SQLException ex) { } - // insert admin user + // insert admin user, but leave the account disabled until we set a + // password with the user authenticator long id = 2; String username = "admin"; String firstname = "admin"; String lastname = "cloud"; - String password = "password"; - - MessageDigest md5 = null; - try { - md5 = MessageDigest.getInstance("MD5"); - } catch (NoSuchAlgorithmException e) { - return; - } - - md5.reset(); - BigInteger pwInt = new BigInteger(1, md5.digest(password.getBytes())); - String pwStr = pwInt.toString(16); - int padding = 32 - pwStr.length(); - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < padding; i++) { - sb.append('0'); // make sure the MD5 password is 32 digits long - } - sb.append(pwStr); - + // create an account for the admin user first insertSql = "INSERT INTO `cloud`.`account` (id, account_name, type, domain_id) VALUES (" + id + ", '" + username + "', '1', '1')"; txn = Transaction.currentTxn(); @@ -376,8 +362,8 @@ public class ConfigurationServerImpl implements ConfigurationServer { } // now insert the user - insertSql = "INSERT INTO `cloud`.`user` (id, username, password, account_id, firstname, lastname, created) " + - "VALUES (" + id + ",'" + username + "','" + sb.toString() + "', 2, '" + firstname + "','" + lastname + "',now())"; + insertSql = "INSERT INTO `cloud`.`user` (id, username, account_id, firstname, lastname, created, state) " + + "VALUES (" + id + ",'" + username + "', 2, '" + firstname + "','" + lastname + "',now(), 'disabled')"; txn = Transaction.currentTxn(); try { @@ -584,6 +570,7 @@ public class ConfigurationServerImpl implements ConfigurationServer { String username = System.getProperty("user.name"); Boolean devel = Boolean.valueOf(_configDao.getValue("developer")); if (!username.equalsIgnoreCase("cloud") && !devel) { + s_logger.warn("Systemvm keypairs could not be set. Management server should be run as cloud user, or in development mode."); return; } String already = _configDao.getValue("ssh.privatekey"); diff --git a/server/src/com/cloud/server/ManagementServer.java b/server/src/com/cloud/server/ManagementServer.java index 473b0ee9b3a..91f82f874d5 100755 --- a/server/src/com/cloud/server/ManagementServer.java +++ b/server/src/com/cloud/server/ManagementServer.java @@ -95,4 +95,6 @@ public interface ManagementServer extends ManagementService { Pair, Integer> searchForStoragePools(Criteria c); String getHashKey(); + + public void enableAdminUser(String password); } diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index 451bdc0c984..7ae71ae7a1c 100755 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -177,6 +177,7 @@ import com.cloud.projects.Project.ListProjectResourcesCriteria; import com.cloud.projects.ProjectManager; import com.cloud.resource.ResourceManager; import com.cloud.server.ResourceTag.TaggedResourceType; +import com.cloud.server.auth.UserAuthenticator; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.DiskOfferingVO; @@ -215,7 +216,9 @@ import com.cloud.user.AccountVO; import com.cloud.user.SSHKeyPair; import com.cloud.user.SSHKeyPairVO; import com.cloud.user.User; +import com.cloud.user.UserAccount; import com.cloud.user.UserContext; +import com.cloud.user.UserVO; import com.cloud.user.dao.AccountDao; import com.cloud.user.dao.SSHKeyPairDao; import com.cloud.user.dao.UserDao; @@ -338,6 +341,8 @@ public class ManagementServerImpl implements ManagementServer { private final StatsCollector _statsCollector; private final Map _availableIdsMap; + + private Adapters _userAuthenticators; private String _hashKey = null; @@ -417,6 +422,11 @@ public class ManagementServerImpl implements ManagementServer { for (String id : availableIds) { _availableIdsMap.put(id, true); } + + _userAuthenticators = locator.getAdapters(UserAuthenticator.class); + if (_userAuthenticators == null || !_userAuthenticators.isSet()) { + s_logger.error("Unable to find an user authenticator."); + } } protected Map getConfigs() { @@ -3587,5 +3597,28 @@ public class ManagementServerImpl implements ManagementServer { } } + + public void enableAdminUser(String password) { + String encodedPassword = null; + + UserVO adminUser = _userDao.getUser(2); + if (adminUser.getState() == Account.State.disabled) { + // This means its a new account, set the password using the authenticator + + for (Enumeration en = _userAuthenticators.enumeration(); en.hasMoreElements();) { + UserAuthenticator authenticator = en.nextElement(); + encodedPassword = authenticator.encode(password); + if (encodedPassword != null) { + break; + } + } + + adminUser.setPassword(encodedPassword); + adminUser.setState(Account.State.enabled); + _userDao.persist(adminUser); + s_logger.info("Admin user enabled"); + } + + } } diff --git a/server/src/com/cloud/server/auth/UserAuthenticator.java b/server/src/com/cloud/server/auth/UserAuthenticator.java index 725516c096f..95c4f0e4707 100644 --- a/server/src/com/cloud/server/auth/UserAuthenticator.java +++ b/server/src/com/cloud/server/auth/UserAuthenticator.java @@ -34,4 +34,10 @@ public interface UserAuthenticator extends Adapter { * @return true if the user has been successfully authenticated, false otherwise */ public boolean authenticate(String username, String password, Long domainId, Map requestParameters); + + /** + * @param password + * @return the encoded password + */ + public String encode(String password); } diff --git a/server/src/com/cloud/servlet/CloudStartupServlet.java b/server/src/com/cloud/servlet/CloudStartupServlet.java index c3e5a8235ce..9efb4ea5a8c 100755 --- a/server/src/com/cloud/servlet/CloudStartupServlet.java +++ b/server/src/com/cloud/servlet/CloudStartupServlet.java @@ -47,6 +47,7 @@ public class CloudStartupServlet extends HttpServlet implements ServletContextLi c.persistDefaultValues(); s_locator = ComponentLocator.getLocator(ManagementServer.Name); ManagementServer ms = (ManagementServer)ComponentLocator.getComponent(ManagementServer.Name); + ms.enableAdminUser("password"); ApiServer.initApiServer(ms.getApiConfig()); } catch (InvalidParameterValueException ipve) { s_logger.error("Exception starting management server ", ipve); diff --git a/server/src/com/cloud/user/AccountManagerImpl.java b/server/src/com/cloud/user/AccountManagerImpl.java index 9d6dd76dc27..78fccb7410d 100755 --- a/server/src/com/cloud/user/AccountManagerImpl.java +++ b/server/src/com/cloud/user/AccountManagerImpl.java @@ -921,7 +921,18 @@ public class AccountManagerImpl implements AccountManager, AccountService, Manag } if (password != null) { - user.setPassword(password); + String encodedPassword = null; + for (Enumeration en = _userAuthenticators.enumeration(); en.hasMoreElements();) { + UserAuthenticator authenticator = en.nextElement(); + encodedPassword = authenticator.encode(password); + if (encodedPassword != null) { + break; + } + } + if (encodedPassword == null) { + throw new CloudRuntimeException("Failed to encode password"); + } + user.setPassword(encodedPassword); } if (email != null) { user.setEmail(email); @@ -1670,7 +1681,20 @@ public class AccountManagerImpl implements AccountManager, AccountService, Manag if (s_logger.isDebugEnabled()) { s_logger.debug("Creating user: " + userName + ", accountId: " + accountId + " timezone:" + timezone); } - UserVO user = _userDao.persist(new UserVO(accountId, userName, password, firstName, lastName, email, timezone)); + + String encodedPassword = null; + for (Enumeration en = _userAuthenticators.enumeration(); en.hasMoreElements();) { + UserAuthenticator authenticator = en.nextElement(); + encodedPassword = authenticator.encode(password); + if (encodedPassword != null) { + break; + } + } + if (encodedPassword == null) { + throw new CloudRuntimeException("Failed to encode password"); + } + + UserVO user = _userDao.persist(new UserVO(accountId, userName, encodedPassword, firstName, lastName, email, timezone)); return user; } diff --git a/test/integration/smoke/test_secondary_storage.py b/test/integration/smoke/test_secondary_storage.py index d345bcb56b8..80b7bfb8fe3 100644 --- a/test/integration/smoke/test_secondary_storage.py +++ b/test/integration/smoke/test_secondary_storage.py @@ -5,9 +5,9 @@ # to you 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 @@ -28,35 +28,14 @@ from nose.plugins.attrib import attr #Import System modules import time -class Services: - """Test secondary storage Services - """ - - def __init__(self): - self.services = { - "storage": { - "url": "nfs://192.168.100.131/SecStorage" - # Format: File_System_Type/Location/Path - }, - "hypervisors": { - 0: { - "hypervisor": "XenServer", - "templatefilter": "self", - }, - }, - "sleep": 60, - "timeout": 5, - } - class TestSecStorageServices(cloudstackTestCase): - + @classmethod def setUpClass(cls): cls.api_client = super(TestSecStorageServices, cls).getClsTestClient().getApiClient() - cls.services = Services().services cls._cleanup = [] return - + @classmethod def tearDownClass(cls): try: @@ -65,16 +44,32 @@ class TestSecStorageServices(cloudstackTestCase): except Exception as e: raise Exception("Warning: Exception during cleanup : %s" % e) return - - def setUp(self): + def setUp(self): self.apiclient = self.testClient.getApiClient() self.cleanup = [] - self.services = Services().services # Get Zone and pod - self.domain = get_domain(self.apiclient, self.services) - self.zone = get_zone(self.apiclient, self.services) - self.pod = get_pod(self.apiclient, self.zone.id) + self.zones = [] + self.pods = [] + for zone in self.config.zones: + cmd = listZones.listZonesCmd() + cmd.name = zone.name + z = self.apiclient.listZones(cmd) + if isinstance(z, list) and len(z) > 0: + self.zones.append(z[0].id) + for pod in zone.pods: + podcmd = listPods.listPodsCmd() + podcmd.zoneid = z[0].id + p = self.apiclient.listPods(podcmd) + if isinstance(p, list) and len(p) >0: + self.pods.append(p[0].id) + + self.domains = [] + dcmd = listDomains.listDomainsCmd() + domains = self.apiclient.listDomains(dcmd) + assert isinstance(domains, list) and len(domains) > 0 + for domain in domains: + self.domains.append(domain.id) return def tearDown(self): @@ -85,62 +80,8 @@ class TestSecStorageServices(cloudstackTestCase): raise Exception("Warning: Exception during cleanup : %s" % e) return - @unittest.skip("skipped - do not add secondary storage") - def test_01_add_sec_storage(self): - """Test secondary storage - """ - - # Validate the following: - # 1. secondary storage should be added to the zone. - # 2. Verify with listHosts and type secondarystorage - - cmd = addSecondaryStorage.addSecondaryStorageCmd() - cmd.zoneid = self.zone.id - cmd.url = self.services["storage"]["url"] - sec_storage = self.apiclient.addSecondaryStorage(cmd) - - self.debug("Added secondary storage to zone: %s" % self.zone.id) - # Cleanup at the end - self._cleanup.append(sec_storage) - - self.assertEqual( - sec_storage.zoneid, - self.zone.id, - "Check zoneid where sec storage is added" - ) - - list_hosts_response = list_hosts( - self.apiclient, - type='SecondaryStorage', - id=sec_storage.id - ) - self.assertEqual( - isinstance(list_hosts_response, list), - True, - "Check list response returns a valid list" - ) - self.assertNotEqual( - len(list_hosts_response), - 0, - "Check list Hosts response" - ) - - host_response = list_hosts_response[0] - #Check if host is Up and running - self.assertEqual( - host_response.id, - sec_storage.id, - "Check ID of secondary storage" - ) - self.assertEqual( - sec_storage.type, - host_response.type, - "Check type of host from list hosts response" - ) - return - @attr(tags = ["advanced", "advancedns", "smoke", "basic", "eip", "sg"]) - def test_02_sys_vm_start(self): + def test_01_sys_vm_start(self): """Test system VM start """ @@ -152,8 +93,6 @@ class TestSecStorageServices(cloudstackTestCase): list_hosts_response = list_hosts( self.apiclient, type='Routing', - zoneid=self.zone.id, - podid=self.pod.id ) self.assertEqual( isinstance(list_hosts_response, list), @@ -176,8 +115,6 @@ class TestSecStorageServices(cloudstackTestCase): # ListStoragePools shows all primary storage pools in UP state list_storage_response = list_storage_pools( self.apiclient, - zoneid=self.zone.id, - podid=self.pod.id ) self.assertEqual( isinstance(list_storage_response, list), @@ -197,58 +134,11 @@ class TestSecStorageServices(cloudstackTestCase): "Check state of primary storage pools is Up or not" ) - # Secondary storage is added successfully - timeout = self.services["timeout"] - while True: - list_hosts_response = list_hosts( - self.apiclient, - type='SecondaryStorageVM', - zoneid=self.zone.id, - ) + list_ssvm_response = list_ssvms( + self.apiclient, + systemvmtype='secondarystoragevm', + ) - if not isinstance(list_hosts_response, list): - # Sleep to ensure Secondary storage is Up - time.sleep(int(self.services["sleep"])) - timeout = timeout - 1 - elif timeout == 0 or isinstance(list_hosts_response, list): - break - - self.assertEqual( - isinstance(list_hosts_response, list), - True, - "Check list response returns a valid list" - ) - - self.assertNotEqual( - len(list_hosts_response), - 0, - "Check list Hosts response" - ) - - host_response = list_hosts_response[0] - #Check if host is Up and running - self.assertEqual( - host_response.state, - 'Up', - "Check state of secondary storage" - ) - self.debug("Checking SSVM status in zone: %s" % self.zone.id) - - timeout = self.services["timeout"] - - while True: - list_ssvm_response = list_ssvms( - self.apiclient, - systemvmtype='secondarystoragevm', - zoneid=self.zone.id, - ) - if not isinstance(list_ssvm_response, list): - # Sleep to ensure SSVMs are Up and Running - time.sleep(int(self.services["sleep"])) - timeout = timeout - 1 - elif timeout == 0 or isinstance(list_ssvm_response, list): - break - self.assertEqual( isinstance(list_ssvm_response, list), True, @@ -270,7 +160,7 @@ class TestSecStorageServices(cloudstackTestCase): return @attr(tags = ["advanced", "advancedns", "smoke", "basic", "eip", "sg"]) - def test_03_sys_template_ready(self): + def test_02_sys_template_ready(self): """Test system templates are ready """ @@ -279,100 +169,50 @@ class TestSecStorageServices(cloudstackTestCase): # 1. wait for listTemplates to show all builtin templates downloaded and # in Ready state - for k, v in self.services["hypervisors"].items(): + hypervisors = {} + for zone in self.config.zones: + for pod in zone.pods: + for cluster in pod.clusters: + hypervisors[cluster.hypervisor] = "self" - self.debug("Downloading BUILTIN templates in zone: %s" % - self.zone.id) - - list_template_response = list_templates( - self.apiclient, - hypervisor=v["hypervisor"], - zoneid=self.zone.id, - templatefilter=v["templatefilter"], - listall=True, - account='system', - domainid=self.domain.id - ) + for zid in self.zones: + for k, v in hypervisors.items(): + self.debug("Checking BUILTIN templates in zone: %s" %zid) + list_template_response = list_templates( + self.apiclient, + hypervisor=k, + zoneid=zid, + templatefilter=v, + listall=True, + account='system' + ) - # Ensure all BUILTIN templates are downloaded - templateid = None - for template in list_template_response: - if template.templatetype == "BUILTIN": - templateid = template.id + # Ensure all BUILTIN templates are downloaded + templateid = None + for template in list_template_response: + if template.templatetype == "BUILTIN": + templateid = template.id - # Wait to start a downloading of template - time.sleep(self.services["sleep"]) - - while True and (templateid != None): - - timeout = self.services["timeout"] - while True: template_response = list_templates( self.apiclient, id=templateid, - zoneid=self.zone.id, - templatefilter=v["templatefilter"], + zoneid=zid, + templatefilter=v, listall=True, - account='system', - domainid=self.domain.id + account='system' ) - if isinstance(template_response, list): template = template_response[0] - break - - elif timeout == 0: - raise Exception("List template API call failed.") - - time.sleep(1) - timeout = timeout - 1 - - # If template is ready, - # template.status = Download Complete - # Downloading - x% Downloaded - # Error - Any other string - if template.status == 'Download Complete' : - break - elif 'Downloaded' not in template.status.split(): - raise Exception - elif 'Downloaded' in template.status.split(): - time.sleep(self.services["sleep"]) + else: + raise Exception("ListTemplate API returned invalid list") - #Ensuring the template is in ready state - time.sleep(self.services["sleep"]) - - timeout = self.services["timeout"] - while True: - template_response = list_templates( - self.apiclient, - id=templateid, - zoneid=self.zone.id, - templatefilter=v["templatefilter"], - listall=True, - account='system', - domainid=self.domain.id - ) - - if isinstance(template_response, list): - template = template_response[0] - break - - elif timeout == 0: - raise Exception("List template API call failed.") - - time.sleep(1) - timeout = timeout - 1 - - self.assertEqual( - isinstance(template_response, list), - True, - "Check list response returns a valid list" - ) - template = template_response[0] + if template.status == 'Download Complete': + self.debug("Template %s is ready in zone %s"%(template.templatetype, zid)) + elif 'Downloaded' not in template.status.split(): + self.debug("templates status is %s"%template.status) - self.assertEqual( - template.isready, - True, - "Check whether state of template is ready or not" - ) - return + self.assertEqual( + template.isready, + True, + "Builtin template is not ready %s in zone %s"%(template.status, zid) + ) diff --git a/tools/apidoc/generateadmincommands.xsl b/tools/apidoc/generateadmincommands.xsl index 5f576c0f887..bb5dc51774d 100644 --- a/tools/apidoc/generateadmincommands.xsl +++ b/tools/apidoc/generateadmincommands.xsl @@ -1,4 +1,22 @@ + diff --git a/tools/apidoc/generatecommand.xsl b/tools/apidoc/generatecommand.xsl index 0d2e8622711..b665cf36f7d 100644 --- a/tools/apidoc/generatecommand.xsl +++ b/tools/apidoc/generatecommand.xsl @@ -1,4 +1,22 @@ + diff --git a/tools/apidoc/generatedomainadmincommands.xsl b/tools/apidoc/generatedomainadmincommands.xsl index 7d60ef2b91f..de67a056f86 100644 --- a/tools/apidoc/generatedomainadmincommands.xsl +++ b/tools/apidoc/generatedomainadmincommands.xsl @@ -1,4 +1,22 @@ + diff --git a/tools/apidoc/generatetoc_footer.xsl b/tools/apidoc/generatetoc_footer.xsl index 5bc2cf46659..cf6dbc4c7b3 100644 --- a/tools/apidoc/generatetoc_footer.xsl +++ b/tools/apidoc/generatetoc_footer.xsl @@ -1,3 +1,21 @@ + diff --git a/tools/apidoc/generateusercommands.xsl b/tools/apidoc/generateusercommands.xsl index ca0eb1f6eaf..fe8ac91d890 100644 --- a/tools/apidoc/generateusercommands.xsl +++ b/tools/apidoc/generateusercommands.xsl @@ -1,4 +1,22 @@ + diff --git a/tools/cli/cloudmonkey/__init__.py b/tools/cli/cloudmonkey/__init__.py new file mode 100644 index 00000000000..e66b2b9e6b0 --- /dev/null +++ b/tools/cli/cloudmonkey/__init__.py @@ -0,0 +1,26 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# Use following rules for versioning: +# .. +# Example: For CloudStack 4.1.x, CLI version should be 0.1.4 +__version__ = "0.0.4" + +try: + from cloudmonkey import * +except ImportError, e: + print e diff --git a/tools/cli/cloudmonkey/cloudmonkey.py b/tools/cli/cloudmonkey/cloudmonkey.py new file mode 100644 index 00000000000..6e08adf918e --- /dev/null +++ b/tools/cli/cloudmonkey/cloudmonkey.py @@ -0,0 +1,382 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +try: + import atexit + import cmd + import clint + import codecs + import logging + import os + import pdb + import readline + import rlcompleter + import sys + import types + + from clint.textui import colored + from ConfigParser import ConfigParser, SafeConfigParser + + from marvin.cloudstackConnection import cloudConnection + from marvin.cloudstackException import cloudstackAPIException + from marvin.cloudstackAPI import * + from marvin import cloudstackAPI +except ImportError, e: + print "Import error in %s : %s" % (__name__, e) + import sys + sys.exit() + +log_fmt = '%(asctime)s - %(filename)s:%(lineno)s - [%(levelname)s] %(message)s' +logger = logging.getLogger(__name__) +completions = cloudstackAPI.__all__ + + +class CloudStackShell(cmd.Cmd): + intro = "☁ Apache CloudStack CLI. Type help or ? to list commands.\n" + ruler = "-" + config_file = os.path.expanduser('~/.cloudmonkey_config') + grammar = [] + + # datastructure {'list': {'users': ['listUsers', [params], docstring]}} + cache_verbs = {} + + def __init__(self): + self.config_fields = {'host': 'localhost', 'port': '8080', + 'apiKey': '', 'secretKey': '', + 'prompt': '🙉 cloudmonkey> ', 'color': 'true', + 'log_file': + os.path.expanduser('~/.cloudmonkey_log'), + 'history_file': + os.path.expanduser('~/.cloudmonkey_history')} + if os.path.exists(self.config_file): + config = self.read_config() + else: + for key in self.config_fields.keys(): + setattr(self, key, self.config_fields[key]) + config = self.write_config() + print("Set your apiKey, secretKey, host, port, prompt, color, " + "log_file, history_file using the set command!") + + for key in self.config_fields.keys(): + setattr(self, key, config.get('CLI', key)) + + self.prompt += " " # Cosmetic fix for prompt + logging.basicConfig(filename=self.log_file, + level=logging.DEBUG, format=log_fmt) + self.logger = logging.getLogger(self.__class__.__name__) + + cmd.Cmd.__init__(self) + # Update config if config_file does not exist + if not os.path.exists(self.config_file): + config = self.write_config() + + # Fix autocompletion issue + if sys.platform == "darwin": + readline.parse_and_bind("bind ^I rl_complete") + else: + readline.parse_and_bind("tab: complete") + + # Enable history support + try: + if os.path.exists(self.history_file): + readline.read_history_file(self.history_file) + atexit.register(readline.write_history_file, self.history_file) + except IOError: + print("Error: history support") + + def read_config(self): + config = ConfigParser() + try: + with open(self.config_file, 'r') as cfg: + config.readfp(cfg) + for section in config.sections(): + for option in config.options(section): + logger.debug("[%s] %s=%s" % (section, option, + config.get(section, option))) + except IOError, e: + self.print_shell("Error: config_file not found", e) + return config + + def write_config(self): + config = ConfigParser() + config.add_section('CLI') + for key in self.config_fields.keys(): + config.set('CLI', key, getattr(self, key)) + with open(self.config_file, 'w') as cfg: + config.write(cfg) + return config + + def emptyline(self): + pass + + def set_grammar(self, grammar): + self.grammar = grammar + + def print_shell(self, *args): + try: + for arg in args: + if isinstance(type(args), types.NoneType): + continue + if self.color == 'true': + if str(arg).count(self.ruler) == len(str(arg)): + print colored.green(arg), + elif 'type' in arg: + print colored.green(arg), + elif 'state' in arg: + print colored.yellow(arg), + elif 'id =' in arg: + print colored.cyan(arg), + elif 'name =' in arg: + print colored.magenta(arg), + elif 'Error' in arg: + print colored.red(arg), + elif ':' in arg: + print colored.blue(arg), + else: + print arg, + else: + print arg, + print + except Exception, e: + print colored.red("Error: "), e + + # FIXME: Fix result processing and printing + def print_result(self, result, response, api_mod): + def print_result_as_list(): + if result is None: + return + for node in result: + print_result_as_instance(node) + + def print_result_as_instance(node): + for attribute in dir(response): + if "__" not in attribute: + attribute_value = getattr(node, attribute) + if isinstance(attribute_value, list): + self.print_shell("\n%s:" % attribute) + try: + self.print_result(attribute_value, + getattr(api_mod, attribute)(), + api_mod) + except AttributeError, e: + pass + elif attribute_value is not None: + self.print_shell("%s = %s" % + (attribute, attribute_value)) + self.print_shell(self.ruler * 80) + + if result is None: + return + + if type(result) is types.InstanceType: + print_result_as_instance(result) + elif isinstance(result, list): + print_result_as_list() + elif isinstance(result, str): + print result + elif isinstance(type(result), types.NoneType): + print_result_as_instance(result) + elif not (str(result) is None): + self.print_shell(result) + + def do_quit(self, s): + """ + Quit Apache CloudStack CLI + """ + self.print_shell("Bye!") + return True + + def do_shell(self, args): + """ + Execute shell commands using shell or ! + Example: !ls or shell ls + """ + os.system(args) + + def make_request(self, command, requests={}): + conn = cloudConnection(self.host, port=int(self.port), + apiKey=self.apiKey, securityKey=self.secretKey, + logging=logging.getLogger("cloudConnection")) + try: + response = conn.make_request(command, requests) + except cloudstackAPIException, e: + self.print_shell("API Error", e) + return None + return response + + def get_api_module(self, api_name, api_class_strs=[]): + try: + api_mod = __import__("marvin.cloudstackAPI.%s" % api_name, + globals(), locals(), api_class_strs, -1) + except ImportError, e: + self.print_shell("Error: API %s not found!" % e) + return None + return api_mod + + def default(self, args): + args = args.strip().split(" ") + api_name = args[0] + + try: + api_cmd_str = "%sCmd" % api_name + api_rsp_str = "%sResponse" % api_name + api_mod = self.get_api_module(api_name, [api_cmd_str, api_rsp_str]) + api_cmd = getattr(api_mod, api_cmd_str) + api_rsp = getattr(api_mod, api_rsp_str) + except AttributeError, e: + self.print_shell("Error: API %s not found!" % e) + return + + command = api_cmd() + response = api_rsp() + #FIXME: Parsing logic + args_dict = dict(map(lambda x: x.split("="), + args[1:])[x] for x in range(len(args) - 1)) + + for attribute in dir(command): + if attribute in args_dict: + setattr(command, attribute, args_dict[attribute]) + + result = self.make_request(command, response) + try: + self.print_result(result, response, api_mod) + except Exception as e: + self.print_shell("🙈 Error on parsing and printing", e) + + def cache_verb_miss(self, verb): + completions_found = filter(lambda x: x.startswith(verb), completions) + self.cache_verbs[verb] = {} + for api_name in completions_found: + try: + api_cmd_str = "%sCmd" % api_name + api_mod = self.get_api_module(api_name, [api_cmd_str]) + api_cmd = getattr(api_mod, api_cmd_str) + doc = api_mod.__doc__ + except AttributeError, e: + self.print_shell("Error: API attribute %s not found!" % e) + params = filter(lambda x: '__' not in x and 'required' not in x, + dir(api_cmd())) + api_name_lower = api_name.replace(verb, '').lower() + self.cache_verbs[verb][api_name_lower] = [api_name, params, doc] + + def completedefault(self, text, line, begidx, endidx): + partitions = line.partition(" ") + verb = partitions[0] + rline = partitions[2].partition(" ") + subject = rline[0] + separator = rline[1] + params = rline[2] + + if verb not in self.grammar: + return [] + + autocompletions = [] + search_string = "" + + if verb not in self.cache_verbs: + self.cache_verb_miss(verb) + + if separator != " ": # Complete verb subjects + autocompletions = self.cache_verbs[verb].keys() + search_string = subject + else: # Complete subject params + autocompletions = self.cache_verbs[verb][subject][1] + search_string = text + + return [s for s in autocompletions if s.startswith(search_string)] + + def do_api(self, args): + """ + Make raw api calls. Syntax: api =. Example: + api listAccount listall=true + """ + if len(args) > 0: + return self.default(args) + else: + self.print_shell("Please use a valid syntax") + + def complete_api(self, text, line, begidx, endidx): + mline = line.partition(" ")[2] + offs = len(mline) - len(text) + return [s[offs:] for s in completions if s.startswith(mline)] + + def do_set(self, args): + """ + Set config for CloudStack CLI. Available options are: + host, port, apiKey, secretKey, log_file, history_file + """ + args = args.split(' ') + if len(args) == 2: + key, value = args + # Note: keys and fields should have same names + setattr(self, key, value) + self.write_config() + else: + self.print_shell("Please use the syntax: set valid-key value") + + def complete_set(self, text, line, begidx, endidx): + mline = line.partition(" ")[2] + offs = len(mline) - len(text) + return [s[offs:] for s in + ['host', 'port', 'apiKey', 'secretKey', 'prompt', 'color', + 'log_file', 'history_file'] if s.startswith(mline)] + + +def main(): + # Add verbs in grammar + grammar = ['create', 'list', 'delete', 'update', + 'enable', 'disable', 'add', 'remove', 'attach', 'detach', + 'assign', 'authorize', 'change', 'register', + 'start', 'restart', 'reboot', 'stop', 'reconnect', + 'cancel', 'destroy', 'revoke', + 'copy', 'extract', 'migrate', 'restore', + 'get', 'prepare', 'deploy', 'upload'] + + self = CloudStackShell + for rule in grammar: + def add_grammar(rule): + def grammar_closure(self, args): + if not rule in self.cache_verbs: + self.cache_verb_miss(rule) + try: + args_partition = args.partition(" ") + res = self.cache_verbs[rule][args_partition[0]] + except KeyError, e: + self.print_shell("Error: no such command on %s" % rule) + return + if '--help' in args: + self.print_shell(res[2]) + return + self.default(res[0] + " " + args_partition[2]) + return grammar_closure + + grammar_handler = add_grammar(rule) + grammar_handler.__doc__ = "%ss resources" % rule.capitalize() + grammar_handler.__name__ = 'do_' + rule + setattr(self, grammar_handler.__name__, grammar_handler) + + shell = CloudStackShell() + shell.set_grammar(grammar) + if len(sys.argv) > 1: + shell.onecmd(' '.join(sys.argv[1:])) + else: + shell.cmdloop() + +if __name__ == "__main__": + main() diff --git a/tools/cli/pom.xml b/tools/cli/pom.xml new file mode 100644 index 00000000000..e7d435bacc9 --- /dev/null +++ b/tools/cli/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + cloud-cli + Apache CloudStack Developer Tools: cloudmonkey cli + pom + + org.apache.cloudstack + cloudstack + 4.1.0-SNAPSHOT + ../../pom.xml + + + + install + + + org.codehaus.mojo + exec-maven-plugin + 1.2.1 + + + compile + compile + + exec + + + ${basedir} + cp + + -rv + ${basedir}/../marvin/marvin + ${basedir}/cloudmonkey + + + + + package + compile + + exec + + + ${basedir} + python + + setup.py + sdist + + + + + + + + diff --git a/tools/cli/setup.py b/tools/cli/setup.py new file mode 100644 index 00000000000..fdbdb2ba0d3 --- /dev/null +++ b/tools/cli/setup.py @@ -0,0 +1,59 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +try: + from setuptools import setup, find_packages +except ImportError: + from distribute_setup import use_setuptools + use_setuptools() + from setuptools import setup, find_packages + +from cloudmonkey import __version__ + +name = 'cloudmonkey' +version = __version__ + +setup( + name = name, + version = version, + author = "The Apache CloudStack Team", + author_email = "cloudstack-dev@incubator.apache.org", + maintainer = "Rohit Yadav", + maintainer_email = "bhaisaab@apache.org", + url = "http://incubator.apache.org/cloudstack", + description = "Command Line Interface for Apache CloudStack", + license = 'ASL 2.0', + packages=find_packages(), + install_requires=['clint'], + include_package_data = True, + zip_safe = False, + classifiers = [ + "Development Status :: 4 - Beta", + "Environment :: Console", + "Intended Audience :: Developers", + "Intended Audience :: End Users/Desktop", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Topic :: Software Development :: Testing", + "Topic :: Software Development :: Interpreters", + "Topic :: Utilities", + ], + entry_points=""" + [console_scripts] + cloudmonkey = cloudmonkey:main + """, +) diff --git a/tools/marvin/marvin/cloudstackConnection.py b/tools/marvin/marvin/cloudstackConnection.py index d70c192406e..98c021437f7 100644 --- a/tools/marvin/marvin/cloudstackConnection.py +++ b/tools/marvin/marvin/cloudstackConnection.py @@ -69,16 +69,21 @@ class cloudConnection(object): try: self.connection = urllib2.urlopen("http://%s:%d/client/api?%s"%(self.mgtSvr, self.port, requestUrl)) - self.logging.debug("sending GET request: %s"%requestUrl) + if self.logging is not None: + self.logging.debug("sending GET request: %s"%requestUrl) response = self.connection.read() - self.logging.info("got response: %s"%response) + if self.logging is not None: + self.logging.info("got response: %s"%response) except IOError, e: if hasattr(e, 'reason'): - self.logging.critical("failed to reach %s because of %s"%(self.mgtSvr, e.reason)) + if self.logging is not None: + self.logging.critical("failed to reach %s because of %s"%(self.mgtSvr, e.reason)) elif hasattr(e, 'code'): - self.logging.critical("server returned %d error code"%e.code) + if self.logging is not None: + self.logging.critical("server returned %d error code"%e.code) except httplib.HTTPException, h: - self.logging.debug("encountered http Exception %s"%h.args) + if self.logging is not None: + self.logging.debug("encountered http Exception %s"%h.args) if self.retries > 0: self.retries = self.retries - 1 self.make_request_with_auth(command, requests) @@ -95,9 +100,11 @@ class cloudConnection(object): requestUrl = "&".join(["=".join([request[0], urllib.quote_plus(str(request[1]))]) for request in requests]) self.connection = urllib2.urlopen("http://%s:%d/client/api?%s"%(self.mgtSvr, self.port, requestUrl)) - self.logging.debug("sending GET request without auth: %s"%requestUrl) + if self.logging is not None: + self.logging.debug("sending GET request without auth: %s"%requestUrl) response = self.connection.read() - self.logging.info("got response: %s"%response) + if self.logging is not None: + self.logging.info("got response: %s"%response) return response def pollAsyncJob(self, jobId, response): @@ -114,7 +121,8 @@ class cloudConnection(object): return asyncResonse time.sleep(5) - self.logging.debug("job: %s still processing, will timeout in %ds"%(jobId, timeout)) + if self.logging is not None: + self.logging.debug("job: %s still processing, will timeout in %ds"%(jobId, timeout)) timeout = timeout - 5 raise cloudstackException.cloudstackAPIException("asyncquery", "Async job timeout %s"%jobId) diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css index 93578037168..db5f0931651 100644 --- a/ui/css/cloudstack3.css +++ b/ui/css/cloudstack3.css @@ -6816,6 +6816,12 @@ label.error { width: 98%; } +.multi-wizard.zone-wizard .multi-edit table th, +.multi-wizard.zone-wizard .multi-edit table td { + min-width: 97px; + max-width: 97px; +} + .multi-wizard.zone-wizard .multi-edit .data { width: 102%; float: left; @@ -7181,6 +7187,10 @@ div.panel.ui-dialog div.list-view div.fixed-header { border-top: none; } +.multi-edit table th { + min-width: 120px; +} + .detail-group .multi-edit table td { border-left: 1px solid #CDCCCC; } @@ -7189,6 +7199,11 @@ div.panel.ui-dialog div.list-view div.fixed-header { width: 70%; } +.detail-view .multi-edit select { + width: 93%; + font-size: 10px; +} + .multi-edit input { width: 85%; } @@ -7202,9 +7217,10 @@ div.panel.ui-dialog div.list-view div.fixed-header { } .multi-edit .range input { - width: 70px; + width: 41px; margin-left: 13px; position: relative; + float: left; } .multi-edit .range label { @@ -7266,8 +7282,9 @@ div.panel.ui-dialog div.list-view div.fixed-header { border-radius: 5px; border-radius: 5px 5px 5px 5px; width: 74px; + text-indent: 0px; text-align: center; - padding: 6px 9px 4px 0px; + padding: 6px 0px 4px; background: url(../images/bg-gradients.png) repeat-x 0px -220px; /*+placement:shift 4px 0px;*/ position: relative; @@ -7389,10 +7406,18 @@ div.panel.ui-dialog div.list-view div.fixed-header { border-left: none; border-right: 1px solid #CFC9C9; height: 15px; - overflow: hidden; + overflow: auto; padding-right: 0; } +.multi-edit .data .data-body .data-item > table tbody tr td span { + overflow-x: auto; + overflow-y: hidden; + max-width: 88px; + display: block; + float: left; +} + .multi-edit .data .data-body .data-item table tbody tr td.blank { } @@ -7401,6 +7426,7 @@ div.panel.ui-dialog div.list-view div.fixed-header { } .multi-edit .data .data-body .data-item table tbody tr td.name span { + width: 53px; color: #4C5D78; font-weight: bold; } @@ -7437,6 +7463,7 @@ div.panel.ui-dialog div.list-view div.fixed-header { .multi-edit .data .data-body .data-item tr td .expand { width: 14px; height: 15px; + display: block; cursor: pointer; background: #FFFFFF url(../images/sprites.png) -541px -499px; border: 1px solid #D0D0D0; @@ -8025,7 +8052,7 @@ div.panel.ui-dialog div.list-view div.fixed-header { max-width: 98px; max-height: 21px; padding: 7px; - font-size: 14px; + font-size: 10px; position: absolute; overflow: hidden; color: #485563; @@ -9525,7 +9552,8 @@ div.panel.ui-dialog div.list-view div.fixed-header { .tooltip-box { width: 15%; height: auto; - display: inline-block, padding: 4px; + display: inline-block; + padding: 4px; background: #FFFFFF; border: 1px solid #BEB8B8; padding: 10px; @@ -10331,6 +10359,13 @@ div.ui-dialog div.acl div.multi-edit div.data div.data-body div.data-item table max-height: 600px; } +div.container div.panel div#details-tab-network.detail-group div div.multi-edit table.multi-edit tbody tr td, +div.container div.panel div#details-tab-network.detail-group div div.multi-edit table.multi-edit thead tr th { + min-width: 80px; + max-width: 80px; + font-size: 10px; +} + .ui-dialog div.autoscaler .detail-actions { } diff --git a/ui/scripts/accounts.js b/ui/scripts/accounts.js index a74389d505f..324c5f56988 100644 --- a/ui/scripts/accounts.js +++ b/ui/scripts/accounts.js @@ -787,24 +787,19 @@ firstname: { label: 'label.first.name' }, lastname: { label: 'label.last.name' } }, - dataProvider: function(args) { - var array1 = []; - if(args.filterBy != null) { - if(args.filterBy.search != null && args.filterBy.search.by != null && args.filterBy.search.value != null) { - switch(args.filterBy.search.by) { - case "name": - if(args.filterBy.search.value.length > 0) - array1.push("&keyword=" + args.filterBy.search.value); - break; - } - } - } - + dataProvider: function(args) { var accountObj = args.context.accounts[0]; + if(isAdmin() || isDomainAdmin()) { + var data = { + domainid: accountObj.domainid, + account: accountObj.name + }; + listViewDataProvider(args, data); + $.ajax({ - url: createURL("listUsers&domainid=" + accountObj.domainid + "&account=" + todb(accountObj.name) + "&page=" + args.page + "&pagesize=" + pageSize + array1.join("")), - dataType: "json", + url: createURL('listUsers'), + data: data, success: function(json) { args.response.success({ actionFilter: userActionfilter, @@ -893,29 +888,39 @@ action: function(args) { var accountObj = args.context.accounts[0]; - var array1 = []; - array1.push("&username=" + todb(args.data.username)); - + var data = { + username: args.data.username + }; + var password = args.data.password; - if (md5Hashed) - password = $.md5(password); - else - password = todb(password); - array1.push("&password=" + password); - - array1.push("&email=" + todb(args.data.email)); - array1.push("&firstname=" + todb(args.data.firstname)); - array1.push("&lastname=" + todb(args.data.lastname)); - if(args.data.timezone != null && args.data.timezone.length > 0) - array1.push("&timezone=" + todb(args.data.timezone)); - - array1.push("&domainid=" + accountObj.domainid); - array1.push("&account=" + todb(accountObj.name)); - array1.push("&accounttype=" + accountObj.accounttype); - + if (md5Hashed) { + password = $.md5(password); + } + $.extend(data, { + password: password + }); + + $.extend(data, { + email: args.data.email, + firstname: args.data.firstname, + lastname: args.data.lastname + }); + + if(args.data.timezone != null && args.data.timezone.length > 0) { + $.extend(data, { + timezone: args.data.timezone + }); + } + + $.extend(data, { + domainid: accountObj.domainid, + account: accountObj.name, + accounttype: accountObj.accounttype + }); + $.ajax({ - url: createURL("createUser" + array1.join("")), - dataType: "json", + url: createURL('createUser'), + data: data, success: function(json) { var item = json.createuserresponse.user; args.response.success({data: item}); @@ -942,15 +947,17 @@ edit: { label: 'label.edit', action: function(args) { - var array1 = []; - array1.push("&username=" + todb(args.data.username)); - array1.push("&email=" + todb(args.data.email)); - array1.push("&firstname=" + todb(args.data.firstname)); - array1.push("&lastname=" + todb(args.data.lastname)); - array1.push("&timezone=" + todb(args.data.timezone)); + var data = { + id: args.context.users[0].id, + username: args.data.username, + email: args.data.email, + firstname: args.data.firstname, + lastname: args.data.lastname, + timezone: args.data.timezone + }; $.ajax({ - url: createURL("updateUser&id=" + args.context.users[0].id + array1.join("")), - dataType: "json", + url: createURL('updateUser'), + data: data, success: function(json) { var item = json.updateuserresponse.user; args.response.success({data:item}); @@ -993,11 +1000,15 @@ var password = args.data.newPassword; if (md5Hashed) password = $.md5(password); - else - password = todb(password); + + var data = { + id: args.context.users[0].id, + password: password + }; + $.ajax({ - url: createURL("updateUser&id=" + args.context.users[0].id + "&password=" + password), - dataType: "json", + url: createURL('updateUser'), + data: data, async: true, success: function(json) { args.response.success({data: json.updateuserresponse.user}); @@ -1022,10 +1033,12 @@ } }, action: function(args) { + var data = { + id: args.context.users[0].id + }; $.ajax({ - url: createURL("registerUserKeys&id=" + args.context.users[0].id), - dataType: "json", - async: true, + url: createURL('registerUserKeys'), + data: data, success: function(json) { args.response.success({data: json.registeruserkeysresponse.userkeys}); } @@ -1049,10 +1062,12 @@ } }, action: function(args) { + var data = { + id: args.context.users[0].id + }; $.ajax({ - url: createURL("disableUser&id=" + args.context.users[0].id), - dataType: "json", - async: true, + url: createURL('disableUser'), + data: data, success: function(json) { var jid = json.disableuserresponse.jobid; args.response.success( @@ -1086,10 +1101,12 @@ } }, action: function(args) { + var data = { + id: args.context.users[0].id + }; $.ajax({ - url: createURL("enableUser&id=" + args.context.users[0].id), - dataType: "json", - async: true, + url: createURL('enableUser'), + data: data, success: function(json) { args.response.success({data: json.enableuserresponse.user}); }, @@ -1116,10 +1133,12 @@ } }, action: function(args) { + var data = { + id: args.context.users[0].id + }; $.ajax({ - url: createURL("deleteUser&id=" + args.context.users[0].id), - dataType: "json", - async: true, + url: createURL('deleteUser'), + data: data, success: function(json) { args.response.success(); } diff --git a/ui/scripts/configuration.js b/ui/scripts/configuration.js index 6b41cb995a6..dd6b691eee2 100644 --- a/ui/scripts/configuration.js +++ b/ui/scripts/configuration.js @@ -161,34 +161,51 @@ }, action: function(args) { - var array1 = []; - array1.push("&name=" + args.data.name); - array1.push("&displaytext=" + todb(args.data.description)); - array1.push("&storageType=" + todb(args.data.storageType)); - array1.push("&cpuNumber=" + args.data.cpuNumber); - array1.push("&cpuSpeed="+ args.data.cpuSpeed); - array1.push("&memory=" + args.data.memory); + var data = { + issystem: false, + name: args.data.name, + displaytext: args.data.description, + storageType: args.data.storageType, + cpuNumber: args.data.cpuNumber, + cpuSpeed: args.data.cpuSpeed, + memory: args.data.memory + }; + + if(args.data.networkRate != null && args.data.networkRate.length > 0) { + $.extend(data, { + networkrate: args.data.networkRate + }); + } - if(args.data.networkRate != null && args.data.networkRate.length > 0) - array1.push("&networkrate=" + args.data.networkRate); - - array1.push("&offerha=" + (args.data.offerHA == "on")); - - if(args.data.storageTags != null && args.data.storageTags.length > 0) - array1.push("&tags=" + todb(args.data.storageTags)); - - if(args.data.hostTags != null && args.data.hostTags.length > 0) - array1.push("&hosttags=" + todb(args.data.hostTags)); - - array1.push("&limitcpuuse=" + (args.data.cpuCap == "on")); - - if(args.$form.find('.form-item[rel=domainId]').css("display") != "none") - array1.push("&domainid=" + args.data.domainId); + $.extend(data, { + offerha: (args.data.offerHA == "on") + }); + + if(args.data.storageTags != null && args.data.storageTags.length > 0) { + $.extend(data, { + tags: args.data.storageTags + }); + } + + if(args.data.hostTags != null && args.data.hostTags.length > 0) { + $.extend(data, { + hosttags: args.data.hostTags + }); + } + + $.extend(data, { + limitcpuuse: (args.data.cpuCap == "on") + }); + + if(args.$form.find('.form-item[rel=domainId]').css("display") != "none") { + $.extend(data, { + domainid: args.data.domainId + }); + } $.ajax({ - url: createURL("createServiceOffering&issystem=false"+array1.join("")), - dataType: "json", - async: true, + url: createURL('createServiceOffering'), + data: data, success: function(json) { var item = json.createserviceofferingresponse.serviceoffering; args.response.success({data: item}); @@ -208,22 +225,16 @@ }, dataProvider: function(args) { - var array1 = []; - if(args.filterBy != null) { - if(args.filterBy.search != null && args.filterBy.search.by != null && args.filterBy.search.value != null) { - switch(args.filterBy.search.by) { - case "name": - if(args.filterBy.search.value.length > 0) - array1.push("&keyword=" + args.filterBy.search.value); - break; - } - } - } + var data = {}; + listViewDataProvider(args, data); + + $.extend(data, { + issystem: false + }); $.ajax({ - url: createURL("listServiceOfferings&issystem=false&page=" + args.page + "&pagesize=" + pageSize + array1.join("")), - dataType: "json", - async: true, + url: createURL('listServiceOfferings'), + data: data, success: function(json) { var items = json.listserviceofferingsresponse.serviceoffering; args.response.success({ @@ -243,12 +254,14 @@ edit: { label: 'label.edit', action: function(args) { - var array1 = []; - array1.push("&name=" + todb(args.data.name)); - array1.push("&displaytext=" + todb(args.data.displaytext)); + var data = { + id: args.context.serviceOfferings[0].id, + name: args.data.name, + displaytext: args.data.displaytext + }; $.ajax({ - url: createURL("updateServiceOffering&id=" + args.context.serviceOfferings[0].id + array1.join("")), - dataType: "json", + url: createURL('updateServiceOffering'), + data: data, success: function(json) { var item = json.updateserviceofferingresponse.serviceoffering; args.response.success({data: item}); @@ -271,9 +284,12 @@ } }, action: function(args) { + var data = { + id: args.context.serviceOfferings[0].id + }; $.ajax({ - url: createURL("deleteServiceOffering&id=" + args.context.serviceOfferings[0].id), - dataType: "json", + url: createURL('deleteServiceOffering'), + data: data, async: true, success: function(json) { args.response.success(); @@ -340,10 +356,14 @@ } ], - dataProvider: function(args) { + dataProvider: function(args) { + var data = { + issystem: false, + id: args.context.serviceOfferings[0].id + }; $.ajax({ - url: createURL("listServiceOfferings&issystem=false&id=" + args.context.serviceOfferings[0].id), - dataType: "json", + url: createURL('listServiceOfferings'), + data: data, async: true, success: function(json) { var item = json.listserviceofferingsresponse.serviceoffering[0]; @@ -508,35 +528,52 @@ }, action: function(args) { - var array1 = []; - array1.push("&name=" + args.data.name); - array1.push("&displaytext=" + todb(args.data.description)); - array1.push("&systemvmtype=" + todb(args.data.systemvmtype)); - array1.push("&storageType=" + todb(args.data.storageType)); - array1.push("&cpuNumber=" + args.data.cpuNumber); - array1.push("&cpuSpeed="+ args.data.cpuSpeed); - array1.push("&memory=" + args.data.memory); + var data = { + issystem: true, + name: args.data.name, + displaytext: args.data.description, + systemvmtype: args.data.systemvmtype, + storageType: args.data.storageType, + cpuNumber: args.data.cpuNumber, + cpuSpeed: args.data.cpuSpeed, + memory: args.data.memory + }; - if(args.data.networkRate != null && args.data.networkRate.length > 0) - array1.push("&networkrate=" + args.data.networkRate); + if(args.data.networkRate != null && args.data.networkRate.length > 0) { + $.extend(data, { + networkrate: args.data.networkRate + }); + } - array1.push("&offerha=" + (args.data.offerHA == "on")); + $.extend(data, { + offerha: (args.data.offerHA == "on") + }); + + if(args.data.storageTags != null && args.data.storageTags.length > 0) { + $.extend(data, { + tags: args.data.storageTags + }); + } - if(args.data.storageTags != null && args.data.storageTags.length > 0) - array1.push("&tags=" + todb(args.data.storageTags)); + if(args.data.hostTags != null && args.data.hostTags.length > 0) { + $.extend(data, { + hosttags: args.data.hostTags + }); + } - if(args.data.hostTags != null && args.data.hostTags.length > 0) - array1.push("&hosttags=" + todb(args.data.hostTags)); - - array1.push("&limitcpuuse=" + (args.data.cpuCap == "on")); - - if(args.$form.find('.form-item[rel=domainId]').css("display") != "none") - array1.push("&domainid=" + args.data.domainId); + $.extend(data, { + limitcpuuse: (args.data.cpuCap == "on") + }); + + if(args.$form.find('.form-item[rel=domainId]').css("display") != "none") { + $.extend(data, { + domainid: args.data.domainId + }); + } $.ajax({ - url: createURL("createServiceOffering&issystem=true"+array1.join("")), - dataType: "json", - async: true, + url: createURL('createServiceOffering'), + data: data, success: function(json) { var item = json.createserviceofferingresponse.serviceoffering; args.response.success({data: item}); @@ -556,22 +593,16 @@ }, dataProvider: function(args) { - var array1 = []; - if(args.filterBy != null) { - if(args.filterBy.search != null && args.filterBy.search.by != null && args.filterBy.search.value != null) { - switch(args.filterBy.search.by) { - case "name": - if(args.filterBy.search.value.length > 0) - array1.push("&keyword=" + args.filterBy.search.value); - break; - } - } - } - + var data = {}; + listViewDataProvider(args, data); + + $.extend(data, { + issystem: true + }); + $.ajax({ - url: createURL("listServiceOfferings&issystem=true&page=" + args.page + "&pagesize=" + pageSize + array1.join("")), - dataType: "json", - async: true, + url: createURL('listServiceOfferings'), + data: data, success: function(json) { var items = json.listserviceofferingsresponse.serviceoffering; args.response.success({data:items}); @@ -588,12 +619,14 @@ edit: { label: 'label.edit', action: function(args) { - var array1 = []; - array1.push("&name=" + todb(args.data.name)); - array1.push("&displaytext=" + todb(args.data.displaytext)); + var data = { + id: args.context.systemServiceOfferings[0].id, + name: args.data.name, + displaytext: args.data.displaytext + }; $.ajax({ - url: createURL("updateServiceOffering&id=" + args.context.systemServiceOfferings[0].id + array1.join("")), - dataType: "json", + url: createURL('updateServiceOffering'), + data: data, success: function(json) { var item = json.updateserviceofferingresponse.serviceoffering; args.response.success({data: item}); @@ -616,10 +649,12 @@ } }, action: function(args) { + var data = { + id: args.context.systemServiceOfferings[0].id + }; $.ajax({ - url: createURL("deleteServiceOffering&id=" + args.context.systemServiceOfferings[0].id), - dataType: "json", - async: true, + url: createURL('deleteServiceOffering'), + data: data, success: function(json) { args.response.success(); }, @@ -703,11 +738,14 @@ } ], - dataProvider: function(args) { + dataProvider: function(args) { + var data = { + issystem: true, + id: args.context.systemServiceOfferings[0].id + }; $.ajax({ - url: createURL("listServiceOfferings&issystem=true&id=" + args.context.systemServiceOfferings[0].id), - dataType: "json", - async: true, + url: createURL('listServiceOfferings'), + data: data, success: function(json) { var item = json.listserviceofferingsresponse.serviceoffering[0]; args.response.success({ @@ -750,22 +788,12 @@ reorder: cloudStack.api.actions.sort('updateDiskOffering', 'diskOfferings'), dataProvider: function(args) { - var array1 = []; - if(args.filterBy != null) { - if(args.filterBy.search != null && args.filterBy.search.by != null && args.filterBy.search.value != null) { - switch(args.filterBy.search.by) { - case "name": - if(args.filterBy.search.value.length > 0) - array1.push("&keyword=" + args.filterBy.search.value); - break; - } - } - } - + var data = {}; + listViewDataProvider(args, data); + $.ajax({ - url: createURL("listDiskOfferings&page=" + args.page + "&pagesize=" + pageSize + array1.join("")), - dataType: "json", - async: true, + url: createURL('listDiskOfferings'), + data: data, success: function(json) { var items = json.listdiskofferingsresponse.diskoffering; args.response.success({data:items}); @@ -860,25 +888,35 @@ }, action: function(args) { - var array1 = []; - array1.push("&name=" + args.data.name); - array1.push("&displaytext=" + todb(args.data.description)); + var data = { + isMirrored: false, + name: args.data.name, + displaytext: args.data.description, + storageType: args.data.storageType, + customized: (args.data.isCustomized=="on") + }; + + if(args.$form.find('.form-item[rel=disksize]').css("display") != "none") { + $.extend(data, { + disksize: args.data.disksize + }); + } - array1.push("&storageType=" + todb(args.data.storageType)); - array1.push("&customized=" + (args.data.isCustomized=="on")); - if(args.$form.find('.form-item[rel=disksize]').css("display") != "none") - array1.push("&disksize=" + args.data.disksize); + if(args.data.tags != null && args.data.tags.length > 0) { + $.extend(data, { + tags: args.data.tags + }); + } - if(args.data.tags != null && args.data.tags.length > 0) - array1.push("&tags=" + todb(args.data.tags)); - - if(args.$form.find('.form-item[rel=domainId]').css("display") != "none") - array1.push("&domainid=" + args.data.domainId); + if(args.$form.find('.form-item[rel=domainId]').css("display") != "none") { + $.extend(data, { + domainid: args.data.domainId + }); + } $.ajax({ - url: createURL("createDiskOffering&isMirrored=false" + array1.join("")), - dataType: "json", - async: true, + url: createURL('createDiskOffering'), + data: data, success: function(json) { var item = json.creatediskofferingresponse.diskoffering; args.response.success({data: item}); @@ -903,12 +941,14 @@ edit: { label: 'label.edit', action: function(args) { - var array1 = []; - array1.push("&name=" + todb(args.data.name)); - array1.push("&displaytext=" + todb(args.data.displaytext)); + var data = { + id: args.context.diskOfferings[0].id, + name: args.data.name, + displaytext: args.data.displaytext + }; $.ajax({ - url: createURL("updateDiskOffering&id=" + args.context.diskOfferings[0].id + array1.join("")), - dataType: "json", + url: createURL('updateDiskOffering'), + data: data, success: function(json) { var item = json.updatediskofferingresponse.diskoffering; args.response.success({data: item}); @@ -931,10 +971,12 @@ } }, action: function(args) { + var data = { + id: args.context.diskOfferings[0].id + }; $.ajax({ - url: createURL("deleteDiskOffering&id=" + args.context.diskOfferings[0].id), - dataType: "json", - async: true, + url: createURL('deleteDiskOffering'), + data: data, success: function(json) { args.response.success(); }, @@ -990,10 +1032,12 @@ ], dataProvider: function(args) { + var data = { + id: args.context.diskOfferings[0].id + }; $.ajax({ - url: createURL("listDiskOfferings&id=" + args.context.diskOfferings[0].id), - dataType: "json", - async: true, + url: createURL('listDiskOfferings'), + data: data, success: function(json) { var item = json.listdiskofferingsresponse.diskoffering[0]; args.response.success({ @@ -1028,26 +1072,12 @@ }, dataProvider: function(args) { - var array1 = []; - if(args.filterBy != null) { - if(args.filterBy.search != null && args.filterBy.search.by != null && args.filterBy.search.value != null) { - switch(args.filterBy.search.by) { - case "name": - if(args.filterBy.search.value.length > 0) - array1.push("&keyword=" + args.filterBy.search.value); - break; - } - } - } + var data = {}; + listViewDataProvider(args, data); $.ajax({ - url: createURL('listNetworkOfferings' + array1.join("")), - data: { - page: args.page, - pagesize: pageSize - }, - dataType: "json", - async: true, + url: createURL('listNetworkOfferings'), + data: data, success: function(json) { var items = json.listnetworkofferingsresponse.networkoffering; @@ -1743,13 +1773,16 @@ edit: { label: 'label.edit', action: function(args) { - var array1 = []; - array1.push("&name=" + todb(args.data.name)); - array1.push("&displaytext=" + todb(args.data.displaytext)); - array1.push("&availability=" + args.data.availability); + var data = { + id: args.context.networkOfferings[0].id, + name: args.data.name, + displaytext: args.data.displaytext, + availability: args.data.availability + }; + $.ajax({ - url: createURL("updateNetworkOffering&id=" + args.context.networkOfferings[0].id + array1.join("")), - dataType: "json", + url: createURL('updateNetworkOffering'), + data: data, success: function(json) { //if availability is being updated from Required to Optional if(args.context.networkOfferings[0].availability == "Required" && args.data.availability == "Optional") diff --git a/ui/scripts/domains.js b/ui/scripts/domains.js index e6633397925..991e37d7324 100644 --- a/ui/scripts/domains.js +++ b/ui/scripts/domains.js @@ -193,16 +193,20 @@ label: 'label.add.domain', action: function(args) { - var array1 = []; - array1.push("&parentdomainid=" + args.context.domains[0].id); - array1.push("&name=" + todb(args.data.name)); - if(args.data.networkdomain != null && args.data.networkdomain.length > 0) - array1.push("&networkdomain=" + todb(args.data.networkdomain)); + var data = { + parentdomainid: args.context.domains[0].id, + name: args.data.name + }; + + if(args.data.networkdomain != null && args.data.networkdomain.length > 0) { + $.extend(data, { + networkdomain: args.data.networkdomain + }); + } $.ajax({ - url: createURL("createDomain" + array1.join("")), - dataType: "json", - async: false, + url: createURL('createDomain'), + data: data, success: function(json) { var item = json.createdomainresponse.domain; args.response.success({data: item}); diff --git a/ui/scripts/globalSettings.js b/ui/scripts/globalSettings.js index 0ae73ca9c5f..e65a00b290c 100644 --- a/ui/scripts/globalSettings.js +++ b/ui/scripts/globalSettings.js @@ -30,16 +30,14 @@ actions: { edit: { label: 'label.change.value', - action: function(args) { - var name = args.data.jsonObj.name; - var value = args.data.value; - + action: function(args) { + var data = { + name: args.data.jsonObj.name, + value: args.data.value + }; $.ajax({ - url: createURL( - 'updateConfiguration&name=' + name + '&value=' + value - ), - dataType: 'json', - async: true, + url: createURL('updateConfiguration'), + data: data, success: function(json) { var item = json.updateconfigurationresponse.configuration; if(item.category == "Usage") @@ -95,22 +93,12 @@ maxguestslimit: { label: 'label.max.guest.limit' } }, dataProvider: function(args) { - var array1 = []; - if(args.filterBy != null) { - if(args.filterBy.search != null && args.filterBy.search.by != null && args.filterBy.search.value != null) { - switch(args.filterBy.search.by) { - case "name": - if(args.filterBy.search.value.length > 0) - array1.push("&keyword=" + args.filterBy.search.value); - break; - } - } - } - + var data = {}; + listViewDataProvider(args, data); + $.ajax({ - url: createURL("listHypervisorCapabilities&page=" + args.page + "&pagesize=" + pageSize + array1.join("")), - dataType: "json", - async: true, + url: createURL('listHypervisorCapabilities'), + data: data, success: function(json) { var items = json.listhypervisorcapabilitiesresponse.hypervisorCapabilities; args.response.success({data:items}); @@ -127,11 +115,14 @@ edit: { label: 'label.edit', action: function(args) { - var array1 = []; - array1.push("&maxguestslimit=" + todb(args.data.maxguestslimit)); + var data = { + id: args.context.hypervisorCapabilities[0].id, + maxguestslimit: args.data.maxguestslimit + }; + $.ajax({ - url: createURL("updateHypervisorCapabilities&id=" + args.context.hypervisorCapabilities[0].id + array1.join("")), - dataType: "json", + url: createURL('updateHypervisorCapabilities'), + data: data, success: function(json) { var item = json.updatehypervisorcapabilitiesresponse['null']; args.response.success({data: item}); diff --git a/ui/scripts/instanceWizard.js b/ui/scripts/instanceWizard.js index b35369db9d7..1d37f78c79f 100644 --- a/ui/scripts/instanceWizard.js +++ b/ui/scripts/instanceWizard.js @@ -467,9 +467,17 @@ //create new network starts here if(args.data["new-network"] == "create-new-network") { var isCreateNetworkSuccessful = true; + + var data = { + networkOfferingId: args.data["new-network-networkofferingid"], + name: args.data["new-network-name"], + displayText: args.data["new-network-name"], + zoneId: selectedZoneObj.id + }; + $.ajax({ - url: createURL("createNetwork&networkOfferingId="+args.data["new-network-networkofferingid"]+"&name="+todb(args.data["new-network-name"])+"&displayText="+todb(args.data["new-network-name"])+"&zoneId="+selectedZoneObj.id), - dataType: "json", + url: createURL('createNetwork'), + data: data, async: false, success: function(json) { newNetwork = json.createnetworkresponse.network; diff --git a/ui/scripts/network.js b/ui/scripts/network.js index 08307010482..855e1c05023 100644 --- a/ui/scripts/network.js +++ b/ui/scripts/network.js @@ -762,6 +762,7 @@ tabFilter: function(args) { var networkOfferingHavingELB = false; var hasNetworkACL = false; + var isVPC = false; $.ajax({ url: createURL("listNetworkOfferings&id=" + args.context.networks[0].networkofferingid), @@ -770,6 +771,10 @@ success: function(json) { var networkoffering = json.listnetworkofferingsresponse.networkoffering[0]; + if (networkoffering.forvpc) { + isVPC = true; + } + $(networkoffering.service).each(function(){ var thisService = this; @@ -786,13 +791,13 @@ } }); - var hiddenTabs = []; + var hiddenTabs = ['egressRules']; // Disable egress UI, for now if (!networkOfferingHavingELB) { hiddenTabs.push("addloadBalancer"); } - if (!hasNetworkACL) { + if (!hasNetworkACL || isVPC) { hiddenTabs.push('egressRules'); } @@ -956,7 +961,8 @@ dataType: "json", async: true, success: function(json) { - var jsonObj = json.listnetworksresponse.network[0]; + var jsonObj = json.listnetworksresponse.network[0]; + addExtraPropertiesToGuestNetworkObject(jsonObj); args.response.success( { actionFilter: cloudStack.actionFilter.guestNetwork, @@ -3072,10 +3078,9 @@ $.ajax({ url: createURL('removeVpnUser'), data: { - username: args.context.multiRule[0].username, - id: args.context.multiRule[0].domainid, - account: args.context.ipAddresses[0].account, - domainid: args.context.ipAddresses[0].domainid + domainid: args.context.multiRule[0].domainid, + account: args.context.multiRule[0].account, + username: args.context.multiRule[0].username }, dataType: 'json', async: true, @@ -3414,174 +3419,187 @@ } }) }, + egressRules: { title: 'label.egress.rule', - custom: cloudStack.uiCustom.securityRules({ - noSelect: true, - noHeaderActionsColumn: true, - fields: { - 'protocol': { - label: 'label.protocol', - select: function(args) { - args.$select.change(function() { - var $inputs = args.$form.find('th, td'); - var $icmpFields = $inputs.filter(function() { - var name = $(this).attr('rel'); + custom: function(args) { + var context = args.context; - return $.inArray(name, [ - 'icmptype', - 'icmpcode' - ]) > -1; - }); - var $otherFields = $inputs.filter(function() { - var name = $(this).attr('rel'); + return $('
').multiEdit({ + context: context, + noSelect: true, + noHeaderActionsColumn: true, + fields: { + 'cidrlist': { edit: true, label: 'label.cidr' }, + 'protocol': { + label: 'label.protocol', + select: function(args) { + args.$select.change(function() { + var $inputs = args.$form.find('th, td'); + var $icmpFields = $inputs.filter(function() { + var name = $(this).attr('rel'); - return name != 'icmptype' && - name != 'icmpcode' && - name != 'protocol' && - name != 'add-rule' && - name != 'cidr' && - name != 'accountname' && - name != 'securitygroup'; + return $.inArray(name, [ + 'icmptype', + 'icmpcode' + ]) > -1; + }); + var $otherFields = $inputs.filter(function() { + var name = $(this).attr('rel'); + + return name != 'cidrlist' && + name != 'icmptype' && + name != 'icmpcode' && + name != 'protocol' && + name != 'add-rule'; + }); + + if ($(this).val() == 'icmp') { + $icmpFields.show(); + $otherFields.hide(); + } else { + $icmpFields.hide(); + $otherFields.show(); + } }); - if ($(this).val() == 'icmp') { - $icmpFields.show(); - $otherFields.hide(); - } else { - $icmpFields.hide(); - $otherFields.show(); + args.response.success({ + data: [ + { name: 'tcp', description: 'TCP' }, + { name: 'udp', description: 'UDP' }, + { name: 'icmp', description: 'ICMP' } + ] + }); + } + }, + 'startport': { edit: true, label: 'label.start.port' }, + 'endport': { edit: true, label: 'label.end.port' }, + 'icmptype': { edit: true, label: 'ICMP.type', isHidden: true }, + 'icmpcode': { edit: true, label: 'ICMP.code', isHidden: true }, + 'add-rule': { + label: 'label.add', + addButton: true + } + }, + add: { + label: 'label.add', + action: function(args) { + var data = { + protocol: args.data.protocol, + cidrlist: args.data.cidrlist, + trafficType: 'Egress' + }; + + if (args.data.icmptype && args.data.icmpcode) { // ICMP + $.extend(data, { + icmptype: args.data.icmptype, + icmpcode: args.data.icmpcode + }); + } else { // TCP/UDP + $.extend(data, { + startport: args.data.startport, + endport: args.data.endport + }); + } + + // Get Source NAT IP + var sourceNATIP; + + $.ajax({ + url: createURL('listPublicIpAddresses'), + data: { + listAll: true, + associatednetworkid: args.context.networks[0].id + }, + async: false, + success: function(json) { + var ipAddresses = json.listpublicipaddressesresponse.publicipaddress; + + sourceNATIP = $.grep(ipAddresses, function(ipAddress) { + return ipAddress.issourcenat; + })[0]; } }); - args.response.success({ - data: [ - { name: 'tcp', description: 'TCP' }, - { name: 'udp', description: 'UDP' }, - { name: 'icmp', description: 'ICMP' } - ] + data.ipaddressid = sourceNATIP.id; + + $.ajax({ + url: createURL('createFirewallRule'), + data: data, + dataType: 'json', + async: true, + success: function(json) { + var jobId = json.createfirewallruleresponse.jobid; + + args.response.success({ + _custom: { + jobId: jobId + }, + notification: { + label: 'label.add.egress.rule', + poll: pollAsyncJobResult + } + }); + }, + error: function(json) { + args.response.error(parseXMLHttpResponse(json)); + } }); } }, - 'startport': { edit: true, label: 'label.start.port' }, - 'endport': { edit: true, label: 'label.end.port' }, - 'icmptype': { edit: true, label: 'ICMP.type', isHidden: true }, - 'icmpcode': { edit: true, label: 'ICMP.code', isHidden: true }, - 'cidr': { edit: true, label: 'label.cidr', isHidden: true }, - 'accountname': { - edit: true, - label: 'label.account.and.security.group', - isHidden: true, - range: ['accountname', 'securitygroup'] + actions: { + destroy: { + label: 'label.remove.rule', + action: function(args) { + $.ajax({ + url: createURL('deleteFirewallRule'), + data: { + id: args.context.multiRule[0].id + }, + dataType: 'json', + async: true, + success: function(data) { + var jobID = data.deletefirewallruleresponse.jobid; + + args.response.success({ + _custom: { + jobId: jobID + }, + notification: { + label: 'label.remove.egress.rule', + poll: pollAsyncJobResult + } + }); + }, + error: function(json) { + args.response.error(parseXMLHttpResponse(json)); + } + }); + } + } }, - 'add-rule': { - label: 'label.add', - addButton: true - } - }, - add: { - label: 'label.add', - action: function(args) { - var data = { - securitygroupid: args.context.securityGroups[0].id, - protocol: args.data.protocol, - domainid: args.context.securityGroups[0].domainid, - account: args.context.securityGroups[0].account - }; - - // TCP / ICMP - if (args.data.icmptype && args.data.icmpcode) { // ICMP - $.extend(data, { - icmptype: args.data.icmptype, - icmpcode: args.data.icmpcode - }); - } else { // TCP - $.extend(data, { - startport: args.data.startport, - endport: args.data.endport - }); - } - - // CIDR / account - if (args.data.cidr) { - data.cidrlist = args.data.cidr; - } else { - data['usersecuritygrouplist[0].account'] = args.data.accountname; - data['usersecuritygrouplist[0].group'] = args.data.securitygroup; - } - + ignoreEmptyFields: true, + dataProvider: function(args) { $.ajax({ - url: createURL('authorizeSecurityGroupEgress'), - data: data, + url: createURL('listFirewallRules'), + data: { + listAll: true, + networkid: args.context.networks[0].id, + trafficType: 'Egress' + }, dataType: 'json', async: true, - success: function(data) { - var jobId = data.authorizesecuritygroupegressresponse.jobid; - + success: function(json) { + var response = json.listfirewallrulesresponse.firewallrule; + args.response.success({ - _custom: { - jobId: jobId - }, - notification: { - label: 'label.add.egress.rule', - poll: pollAsyncJobResult - } + data: response }); } }); } - }, - actions: { - destroy: { - label: 'label.remove.rule', - action: function(args) { - $.ajax({ - url: createURL('revokeSecurityGroupEgress'), - data: { - domainid: args.context.securityGroups[0].domainid, - account: args.context.securityGroups[0].account, - id: args.context.multiRule[0].id - }, - dataType: 'json', - async: true, - success: function(data) { - var jobID = data.revokesecuritygroupegress.jobid; - - args.response.success({ - _custom: { - jobId: jobID - }, - notification: { - label: 'label.remove.egress.rule', - poll: pollAsyncJobResult - } - }); - } - }); - } - } - }, - ignoreEmptyFields: true, - dataProvider: function(args) { - $.ajax({ - url: createURL('listSecurityGroups'), - data: { - id: args.context.securityGroups[0].id - }, - dataType: 'json', - async: true, - success: function(data) { - args.response.success({ - data: $.map( - data.listsecuritygroupsresponse.securitygroup[0].egressrule ? - data.listsecuritygroupsresponse.securitygroup[0].egressrule : [], - ingressEgressDataMap - ) - }); - } - }); - } - }) + }); + } } }, @@ -4030,52 +4048,51 @@ }); } }, - - router: { - title: 'VPC Router Details', - fields:[ - { - name: {label:'label.name'} - }, - { - id:{ label:'label.id'}, - zonename: { label: 'label.zone'}, - dns1: {label: 'label.dns'}, - gateway: {label:'label.gateway'}, - publicip: {label: 'label.public.ip'}, - guestipaddress:{ label: 'label.guest.ip'}, - linklocalip: {label: 'label.linklocal.ip'}, - state: { label:'label.state'}, - serviceofferingname: {label:'label.service.offering'}, - isredundantrouter:{ - label: 'label.redundant.router', - converter: function(booleanValue) { - if (booleanValue == true) { - return "Yes"; - } - return "No"; - } - }, - account: {label:'label.account'}, - domain: {label: 'label.domain'} + router: { + title: 'VPC Router Details', + fields:[ + { + name: {label:'label.name'} + }, + { + id:{ label:'label.id'}, + zonename: { label: 'label.zone'}, + dns1: {label: 'label.dns'}, + gateway: {label:'label.gateway'}, + publicip: {label: 'label.public.ip'}, + guestipaddress:{ label: 'label.guest.ip'}, + linklocalip: {label: 'label.linklocal.ip'}, + state: { label:'label.state'}, + serviceofferingname: {label:'label.service.offering'}, + isredundantrouter:{ + label: 'label.redundant.router', + converter: function(booleanValue) { + if (booleanValue == true) { + return "Yes"; } - ], - dataProvider: function(args) { - $.ajax ({ - url:createURL("listRouters&listAll=true&vpcid=" +args.context.vpc[0].id), - dataType: "json", - async: true, - success:function(json) { - var item = json.listroutersresponse.router[0]; - args.response.success ({ - data:item - }) - } - - }); - } - + return "No"; + } + }, + account: {label:'label.account'}, + domain: {label: 'label.domain'} + } + ], + dataProvider: function(args) { + $.ajax({ + url: createURL("listRouters&listAll=true&vpcid=" +args.context.vpc[0].id), + dataType: "json", + async: true, + success: function(json) { + var item = json.listroutersresponse.router[0]; + + args.response.success({ + actionFilter: cloudStack.sections.system.routerActionFilter, + data:item + }); } + }); + } + } } } } diff --git a/ui/scripts/projects.js b/ui/scripts/projects.js index 34c57c6aae7..8310ecf0e4c 100644 --- a/ui/scripts/projects.js +++ b/ui/scripts/projects.js @@ -569,13 +569,18 @@ // Project listing data provider dataProvider: function(args) { var user = args.context.users[0]; + var data = { + accountId: user.userid, + listAll: true + }; + + if (args.projectName) { + data.keyword = args.projectName; + } $.ajax({ url: createURL('listProjects', { ignoreProject: true }), - data: { - accountId: user.userid, - listAll: true - }, + data: data, dataType: 'json', async: true, success: function(data) { diff --git a/ui/scripts/sharedFunctions.js b/ui/scripts/sharedFunctions.js index 5e187ed1b82..b6b3ef817f6 100644 --- a/ui/scripts/sharedFunctions.js +++ b/ui/scripts/sharedFunctions.js @@ -37,8 +37,8 @@ var ERROR_INTERNET_CANNOT_CONNECT = 12029; var ERROR_VMOPS_ACCOUNT_ERROR = 531; // Default password is MD5 hashed. Set the following variable to false to disable this. -var md5Hashed = true; -var md5HashedLogin = true; +var md5Hashed = false; +var md5HashedLogin = false; //page size for API call (e.g."listXXXXXXX&pagesize=N" ) var pageSize = 20; @@ -454,6 +454,29 @@ function listViewDataProvider(args, data) { }); } +//used by infrastruct page and network page +var addExtraPropertiesToGuestNetworkObject = function(jsonObj) { + jsonObj.networkdomaintext = jsonObj.networkdomain; + jsonObj.networkofferingidText = jsonObj.networkofferingid; + + if(jsonObj.acltype == "Domain") { + if(jsonObj.domainid == rootAccountId) + jsonObj.scope = "All"; + else + jsonObj.scope = "Domain (" + jsonObj.domain + ")"; + } + else if (jsonObj.acltype == "Account"){ + if(jsonObj.project != null) + jsonObj.scope = "Account (" + jsonObj.domain + ", " + jsonObj.project + ")"; + else + jsonObj.scope = "Account (" + jsonObj.domain + ", " + jsonObj.account + ")"; + } + + if(jsonObj.vlan == null && jsonObj.broadcasturi != null) { + jsonObj.vlan = jsonObj.broadcasturi.replace("vlan://", ""); + } +} + //find service object in network object function ipFindNetworkServiceByName(pName, networkObj) { if(networkObj == null) diff --git a/ui/scripts/system.js b/ui/scripts/system.js index 4eadf097b36..5a84bf8027b 100644 --- a/ui/scripts/system.js +++ b/ui/scripts/system.js @@ -194,6 +194,10 @@ podCount: function(data) { $.ajax({ url: createURL('listPods'), + data: { + page: 1, + pagesize: 1 //specifying pagesize as 1 because we don't need any embedded objects to be returned here. The only thing we need from API response is "count" property. + }, success: function(json) { dataFns.clusterCount($.extend(data, { podCount: json.listpodsresponse.count ? @@ -206,11 +210,24 @@ clusterCount: function(data) { $.ajax({ url: createURL('listClusters'), - success: function(json) { - dataFns.hostCount($.extend(data, { + data: { + page: 1, + pagesize: 1 //specifying pagesize as 1 because we don't need any embedded objects to be returned here. The only thing we need from API response is "count" property. + }, + success: function(json) { + dataFns.hostCount($.extend(data, { clusterCount: json.listclustersresponse.count ? json.listclustersresponse.count : 0 })); + + //comment the 4 lines above and uncomment the following 4 lines if listHosts API still responds slowly. + + /* + dataFns.primaryStorageCount($.extend(data, { + clusterCount: json.listclustersresponse.count ? + json.listclustersresponse.count : 0 + })); + */ } }); }, @@ -219,7 +236,9 @@ $.ajax({ url: createURL('listHosts'), data: { - type: 'routing' + type: 'routing', + page: 1, + pagesize: 1 //specifying pagesize as 1 because we don't need any embedded objects to be returned here. The only thing we need from API response is "count" property. }, success: function(json) { dataFns.primaryStorageCount($.extend(data, { @@ -233,11 +252,24 @@ primaryStorageCount: function(data) { $.ajax({ url: createURL('listStoragePools'), - success: function(json) { - dataFns.secondaryStorageCount($.extend(data, { + data: { + page: 1, + pagesize: 1 //specifying pagesize as 1 because we don't need any embedded objects to be returned here. The only thing we need from API response is "count" property. + }, + success: function(json) { + dataFns.secondaryStorageCount($.extend(data, { primaryStorageCount: json.liststoragepoolsresponse.count ? json.liststoragepoolsresponse.count : 0 })); + + //comment the 4 lines above and uncomment the following 4 lines if listHosts API still responds slowly. + + /* + dataFns.systemVmCount($.extend(data, { + primaryStorageCount: json.liststoragepoolsresponse.count ? + json.liststoragepoolsresponse.count : 0 + })); + */ } }); }, @@ -246,7 +278,9 @@ $.ajax({ url: createURL('listHosts'), data: { - type: 'SecondaryStorage' + type: 'SecondaryStorage', + page: 1, + pagesize: 1 //specifying pagesize as 1 because we don't need any embedded objects to be returned here. The only thing we need from API response is "count" property. }, success: function(json) { dataFns.systemVmCount($.extend(data, { @@ -260,6 +294,10 @@ systemVmCount: function(data) { $.ajax({ url: createURL('listSystemVms'), + data: { + page: 1, + pagesize: 1 //specifying pagesize as 1 because we don't need any embedded objects to be returned here. The only thing we need from API response is "count" property. + }, success: function(json) { dataFns.virtualRouterCount($.extend(data, { systemVmCount: json.listsystemvmsresponse.count ? @@ -273,14 +311,18 @@ $.ajax({ url: createURL('listRouters'), data: { - projectid: -1 + projectid: -1, + page: 1, + pagesize: 1 //specifying pagesize as 1 because we don't need any embedded objects to be returned here. The only thing we need from API response is "count" property. }, success: function(json) { var total1 = json.listroutersresponse.count ? json.listroutersresponse.count : 0; $.ajax({ url: createURL('listRouters'), data: { - listAll: true + listAll: true, + page: 1, + pagesize: 1 //specifying pagesize as 1 because we don't need any embedded objects to be returned here. The only thing we need from API response is "count" property. }, success: function(json) { var total2 = json.listroutersresponse.count ? json.listroutersresponse.count : 0; @@ -336,13 +378,10 @@ data: data }); }; - - // re: CS-16413 -- Disable API calls - return args.response.success({ - data: {} - }); - - dataFns.zoneCount({}); + + //dataFns.zoneCount({}); + dataFns.podCount({}); //uncomment the line above and remove this line after "count" in listZones API is fixed. + } }, @@ -10314,7 +10353,7 @@ return allowedActions; } - var routerActionfilter = function(args) { + var routerActionfilter = cloudStack.sections.system.routerActionFilter = function(args) { var jsonObj = args.context.item; var allowedActions = []; @@ -10391,28 +10430,6 @@ } } - var addExtraPropertiesToGuestNetworkObject = function(jsonObj) { - jsonObj.networkdomaintext = jsonObj.networkdomain; - jsonObj.networkofferingidText = jsonObj.networkofferingid; - - if(jsonObj.acltype == "Domain") { - if(jsonObj.domainid == rootAccountId) - jsonObj.scope = "All"; - else - jsonObj.scope = "Domain (" + jsonObj.domain + ")"; - } - else if (jsonObj.acltype == "Account"){ - if(jsonObj.project != null) - jsonObj.scope = "Account (" + jsonObj.domain + ", " + jsonObj.project + ")"; - else - jsonObj.scope = "Account (" + jsonObj.domain + ", " + jsonObj.account + ")"; - } - - if(jsonObj.vlan == null && jsonObj.broadcasturi != null) { - jsonObj.vlan = jsonObj.broadcasturi.replace("vlan://", ""); - } - } - var addExtraPropertiesToRouterInstanceObject = function(jsonObj) { if(jsonObj.isredundantrouter == true) jsonObj["redundantRouterState"] = jsonObj.redundantstate; diff --git a/ui/scripts/templates.js b/ui/scripts/templates.js index f6c209a4a30..c865d740671 100644 --- a/ui/scripts/templates.js +++ b/ui/scripts/templates.js @@ -1428,7 +1428,7 @@ // "Delete Template" //if (((isUser() && jsonObj.ispublic == true && !(jsonObj.domainid == g_domainid && jsonObj.account == g_account))) - if (((isAdmin() == false && !(jsonObj.domainid == g_domainid && jsonObj.account == g_account))) //if neither root-admin, nor item owner + if (((isAdmin() == false && !(jsonObj.domainid == g_domainid && jsonObj.account == g_account) && !(jsonObj.domainid == g_domainid && jsonObj.projectid == cloudStack.context.projects[0].id))) //if neither root-admin, nor the same account, nor the same project || (jsonObj.isready == false && jsonObj.status != null && jsonObj.status.indexOf("Downloaded") != -1) || (jsonObj.account == "system")) { //do nothing @@ -1487,7 +1487,7 @@ // "Delete ISO" //if (((isUser() && jsonObj.ispublic == true && !(jsonObj.domainid == g_domainid && jsonObj.account == g_account))) - if (((isAdmin() == false && !(jsonObj.domainid == g_domainid && jsonObj.account == g_account))) //if neither root-admin, nor item owner + if (((isAdmin() == false && !(jsonObj.domainid == g_domainid && jsonObj.account == g_account) && !(jsonObj.domainid == g_domainid && jsonObj.projectid == cloudStack.context.projects[0].id))) //if neither root-admin, nor the same account, nor the same project || (jsonObj.isready == false && jsonObj.status != null && jsonObj.status.indexOf("Downloaded") != -1) || (jsonObj.account == "system") ) { diff --git a/ui/scripts/ui-custom/instanceWizard.js b/ui/scripts/ui-custom/instanceWizard.js index 6a9a0570d2d..a591d178e16 100644 --- a/ui/scripts/ui-custom/instanceWizard.js +++ b/ui/scripts/ui-custom/instanceWizard.js @@ -137,8 +137,8 @@ ) .append( $('
').addClass('select-desc') - .append($('
').addClass('name').html(this[fields.name])) - .append($('
').addClass('desc').html(this[fields.desc])) + .append($('
').addClass('name').html(_s(this[fields.name]))) + .append($('
').addClass('desc').html(_s(this[fields.desc]))) ) .data('json-obj', this); @@ -876,6 +876,12 @@ } }); + $wizard.find('div.data-disk-offering div.custom-size input[type=text]').bind('change',function() { + var old = $wizard.find('div.data-disk-offering div.custom-size input[type=text]').val(); + $wizard.find('div.data-disk-offering span.custom-disk-size').html(_s(old)); + }); + + return $wizard.dialog({ title: _l('label.vm.add'), width: 800, diff --git a/ui/scripts/ui-custom/projects.js b/ui/scripts/ui-custom/projects.js index 221d834d29f..117c4337726 100644 --- a/ui/scripts/ui-custom/projects.js +++ b/ui/scripts/ui-custom/projects.js @@ -543,8 +543,9 @@ var $cancel = $('
').addClass('button cancel').html(_l('label.cancel')); // Get project data - var loadData = function(complete) { + var loadData = function(complete, options) { cloudStack.projects.dataProvider({ + projectName: options ? options.projectName : null, context: cloudStack.context, response: { success: function(args) { @@ -585,7 +586,9 @@ // Search form $searchForm.submit(function() { $list.find('li').remove(); - loadData(); + loadData(null, { + projectName: $(this).find('input[type=text]').val() + }); return false; }); diff --git a/ui/scripts/ui/widgets/cloudBrowser.js b/ui/scripts/ui/widgets/cloudBrowser.js index 47fbd0a08b5..9a56bb353e9 100644 --- a/ui/scripts/ui/widgets/cloudBrowser.js +++ b/ui/scripts/ui/widgets/cloudBrowser.js @@ -349,6 +349,11 @@ 'cloudBrowser', { 'breadcrumb': function($target, $browser, data) { + + if ($ ('#browser').hasClass('panel-highlight')) { + return false; + } + $browser.cloudBrowser('selectPanel', { panel: data.panel }); } } diff --git a/ui/scripts/vpc.js b/ui/scripts/vpc.js index a79006eab3f..e805282aba0 100644 --- a/ui/scripts/vpc.js +++ b/ui/scripts/vpc.js @@ -251,8 +251,27 @@ routerDetailView: function() { return { title: 'VPC router details', + updateContext: function(args) { + var router; + + $.ajax({ + url: createURL("listRouters&listAll=true&vpcid=" +args.context.vpc[0].id), + dataType: "json", + async: false, + success: function(json) { + router = json.listroutersresponse.router[0]; + } + }); + + return { + routers: [router] + }; + }, + actions: cloudStack.sections.system.subsections.virtualRouters + .listView.detailView.actions, tabs: { - routerDetails: cloudStack.sections.network.sections.vpc.listView.detailView.tabs.router + routerDetails: cloudStack.sections.network.sections.vpc + .listView.detailView.tabs.router } }; }, @@ -1595,7 +1614,8 @@ } }); - var hiddenTabs = []; + var hiddenTabs = ['ipAddresses']; // Disable IP address tab; it is redundant with 'view all' button + if(networkOfferingHavingELB == false) hiddenTabs.push("addloadBalancer"); return hiddenTabs; diff --git a/utils/pom.xml b/utils/pom.xml index 47e20eda59c..2e4e74f5411 100644 --- a/utils/pom.xml +++ b/utils/pom.xml @@ -161,6 +161,7 @@ com/cloud/utils/testcase/*TestCase* + com/cloud/utils/db/*Test*