From 2f2228ac620603203e6b24b068bca0105c77058b Mon Sep 17 00:00:00 2001 From: Miguel Ferreira Date: Fri, 17 Jun 2016 14:56:37 +0200 Subject: [PATCH] Import full content of cosmic tracking repo --- .gitignore | 455 + LICENSE | 1 + README.md | 167 +- build/replace.properties | 33 + cosmic-agent/LICENSE | 201 + cosmic-agent/README.md | 1 + cosmic-agent/bindir/cloud-setup-agent.in | 153 + cosmic-agent/bindir/cloud-ssh.in | 19 + .../bindir/cosmic-agent-profile.sh.in | 20 + cosmic-agent/bindir/cosmic-agent-upgrade.in | 63 + cosmic-agent/bindir/libvirtqemuhook.in | 62 + cosmic-agent/conf/agent.properties | 175 + cosmic-agent/conf/cosmic-agent.logrotate | 26 + .../conf/developer.properties.template | 55 + cosmic-agent/conf/environment.properties.in | 21 + cosmic-agent/conf/log4j-cloud.xml.in | 103 + cosmic-agent/pom.xml | 117 + .../src/main/java/com/cloud/agent/Agent.java | 859 + .../main/java/com/cloud/agent/AgentShell.java | 495 + .../java/com/cloud/agent/IAgentShell.java | 54 + .../com/cloud/agent/dao/StorageComponent.java | 28 + .../agent/dao/impl/PropertiesStorage.java | 147 + .../ConsoleProxyAuthenticationResult.java | 80 + .../consoleproxy/ConsoleProxyResource.java | 463 + .../java/com/cloud/agent/AgentShellTest.java | 47 + .../agent/dao/impl/PropertiesStorageTest.java | 67 + cosmic-client/LICENSE | 201 + cosmic-client/README.md | 1 + .../classes/resources/messages.properties | 2198 +++ cosmic-client/WEB-INF/web.xml | 86 + .../bindir/cloud-setup-management.in | 61 + .../bindir/cloud-update-xenserver-licenses.in | 182 + cosmic-client/cosmic-ui/LICENSE | 201 + cosmic-client/cosmic-ui/README.md | 1 + .../cosmic-ui/css/cloudstack3-ie7.css | 209 + cosmic-client/cosmic-ui/css/cloudstack3.css | 13159 ++++++++++++++ .../cosmic-ui/css/cloudstack3.hu.css | 26 + .../cosmic-ui/css/cloudstack3.ja_JP.css | 83 + cosmic-client/cosmic-ui/css/custom.css | 21 + .../cosmic-ui/css/token-input-facebook.css | 133 + cosmic-client/cosmic-ui/dictionary.jsp | 1108 ++ cosmic-client/cosmic-ui/dictionary2.jsp | 1097 ++ cosmic-client/cosmic-ui/error.jsp | 30 + .../cosmic-ui/images/ajax-loader-small.gif | Bin 0 -> 10781 bytes .../cosmic-ui/images/ajax-loader.gif | Bin 0 -> 12018 bytes .../images/bg-breadcrumb-project-view.png | Bin 0 -> 2860 bytes .../cosmic-ui/images/bg-breadcrumb.png | Bin 0 -> 2843 bytes .../images/bg-breadcrumbs-project-view.png | Bin 0 -> 2857 bytes .../cosmic-ui/images/bg-breadcrumbs.png | Bin 0 -> 3071 bytes .../cosmic-ui/images/bg-button-view-more.png | Bin 0 -> 1057 bytes .../images/bg-details-tab-gradient.png | Bin 0 -> 1125 bytes .../cosmic-ui/images/bg-dialog-body.png | Bin 0 -> 4645 bytes .../cosmic-ui/images/bg-dialog-header.png | Bin 0 -> 1059 bytes .../images/bg-gradient-white-transparent.png | Bin 0 -> 4052 bytes .../cosmic-ui/images/bg-gradients.png | Bin 0 -> 7539 bytes cosmic-client/cosmic-ui/images/bg-header.png | Bin 0 -> 2824 bytes .../images/bg-install-wizard-header.jpg | Bin 0 -> 22737 bytes .../images/bg-install-wizard-header.png | Bin 0 -> 69416 bytes cosmic-client/cosmic-ui/images/bg-login.jpg | Bin 0 -> 38127 bytes cosmic-client/cosmic-ui/images/bg-login.png | Bin 0 -> 8401 bytes cosmic-client/cosmic-ui/images/bg-naas.png | Bin 0 -> 10706 bytes .../bg-nav-item-active-project-view.png | Bin 0 -> 2921 bytes .../cosmic-ui/images/bg-nav-item-active.png | Bin 0 -> 2863 bytes .../images/bg-nav-item-project-view.png | Bin 0 -> 992 bytes .../cosmic-ui/images/bg-nav-item.png | Bin 0 -> 2849 bytes .../cosmic-ui/images/bg-network-nat.png | Bin 0 -> 11534 bytes cosmic-client/cosmic-ui/images/bg-network.png | Bin 0 -> 14452 bytes .../cosmic-ui/images/bg-notifications.png | Bin 0 -> 4074 bytes .../cosmic-ui/images/bg-panel-shadow.png | Bin 0 -> 960 bytes .../cosmic-ui/images/bg-section-switcher.png | Bin 0 -> 1011 bytes .../cosmic-ui/images/bg-status_box.png | Bin 0 -> 2827 bytes .../images/bg-system-chart-compute.png | Bin 0 -> 94812 bytes .../images/bg-system-chart-lines.png | Bin 0 -> 1837 bytes .../images/bg-system-network-traffic.png | Bin 0 -> 13791 bytes .../cosmic-ui/images/bg-table-head.png | Bin 0 -> 1089 bytes .../cosmic-ui/images/bg-transparent-white.png | Bin 0 -> 2944 bytes .../images/bg-what-is-cloudstack.png | Bin 0 -> 37396 bytes cosmic-client/cosmic-ui/images/buttons.png | Bin 0 -> 49486 bytes cosmic-client/cosmic-ui/images/cosmic.ico | Bin 0 -> 2926 bytes .../cosmic-ui/images/destroy-anim.gif | Bin 0 -> 20116 bytes cosmic-client/cosmic-ui/images/gradients.png | Bin 0 -> 11447 bytes .../cosmic-ui/images/header-gradient.png | Bin 0 -> 62651 bytes cosmic-client/cosmic-ui/images/icons.png | Bin 0 -> 61164 bytes .../cosmic-ui/images/infrastructure-icons.png | Bin 0 -> 66823 bytes .../cosmic-ui/images/install-wizard-parts.png | Bin 0 -> 623374 bytes .../images/instance-wizard-parts.png | Bin 0 -> 78547 bytes .../cosmic-ui/images/logo-cosmic.png | Bin 0 -> 3320 bytes cosmic-client/cosmic-ui/images/minus.png | Bin 0 -> 1544 bytes .../cosmic-ui/images/overlay-pattern.png | Bin 0 -> 14969 bytes cosmic-client/cosmic-ui/images/sky.jpg | Bin 0 -> 264429 bytes cosmic-client/cosmic-ui/images/sprites.png | Bin 0 -> 198421 bytes cosmic-client/cosmic-ui/index.jsp | 1811 ++ cosmic-client/cosmic-ui/lib/date.js | 125 + cosmic-client/cosmic-ui/lib/excanvas.js | 1427 ++ .../cosmic-ui/lib/flot/jquery.colorhelpers.js | 179 + .../lib/flot/jquery.flot.crosshair.js | 167 + .../lib/flot/jquery.flot.fillbetween.js | 183 + .../cosmic-ui/lib/flot/jquery.flot.image.js | 238 + .../cosmic-ui/lib/flot/jquery.flot.js | 2599 +++ .../lib/flot/jquery.flot.navigate.js | 336 + .../cosmic-ui/lib/flot/jquery.flot.pie.js | 750 + .../cosmic-ui/lib/flot/jquery.flot.resize.js | 60 + .../lib/flot/jquery.flot.selection.js | 344 + .../cosmic-ui/lib/flot/jquery.flot.stack.js | 184 + .../cosmic-ui/lib/flot/jquery.flot.symbol.js | 70 + .../lib/flot/jquery.flot.threshold.js | 103 + .../css/images/ui-bg_flat_0_aaaaaa_40x100.png | Bin 0 -> 180 bytes .../images/ui-bg_flat_75_ffffff_40x100.png | Bin 0 -> 178 bytes .../images/ui-bg_glass_55_fbf9ee_1x400.png | Bin 0 -> 120 bytes .../images/ui-bg_glass_65_ffffff_1x400.png | Bin 0 -> 105 bytes .../images/ui-bg_glass_75_dadada_1x400.png | Bin 0 -> 111 bytes .../images/ui-bg_glass_75_e6e6e6_1x400.png | Bin 0 -> 110 bytes .../ui-bg_highlight-hard_75_a7c1d2_1x100.png | Bin 0 -> 121 bytes .../ui-bg_inset-soft_95_fef1ec_1x100.png | Bin 0 -> 123 bytes .../css/images/ui-icons_222222_256x240.png | Bin 0 -> 4369 bytes .../css/images/ui-icons_2e83ff_256x240.png | Bin 0 -> 4369 bytes .../css/images/ui-icons_454545_256x240.png | Bin 0 -> 4369 bytes .../css/images/ui-icons_888888_256x240.png | Bin 0 -> 4369 bytes .../css/images/ui-icons_cd0a0a_256x240.png | Bin 0 -> 4369 bytes .../cosmic-ui/lib/jquery-ui/css/jquery-ui.css | 568 + .../cosmic-ui/lib/jquery-ui/index.html | 367 + .../cosmic-ui/lib/jquery-ui/js/jquery-ui.js | 789 + cosmic-client/cosmic-ui/lib/jquery.cookies.js | 96 + cosmic-client/cosmic-ui/lib/jquery.easing.js | 205 + cosmic-client/cosmic-ui/lib/jquery.js | 9046 ++++++++++ cosmic-client/cosmic-ui/lib/jquery.md5.js | 269 + .../cosmic-ui/lib/jquery.tokeninput.js | 928 + .../lib/jquery.validate.additional-methods.js | 928 + .../cosmic-ui/lib/jquery.validate.js | 1357 ++ cosmic-client/cosmic-ui/lib/qunit/qunit.css | 233 + cosmic-client/cosmic-ui/lib/qunit/qunit.js | 1633 ++ cosmic-client/cosmic-ui/lib/require.js | 1993 +++ cosmic-client/cosmic-ui/lib/reset.css | 48 + .../modules/infrastructure/infrastructure.css | 19 + .../modules/infrastructure/infrastructure.js | 75 + cosmic-client/cosmic-ui/modules/modules.js | 22 + cosmic-client/cosmic-ui/modules/vpc/vpc.css | 384 + cosmic-client/cosmic-ui/modules/vpc/vpc.js | 427 + cosmic-client/cosmic-ui/plugins/plugins.js | 20 + cosmic-client/cosmic-ui/scripts/accounts.js | 2017 +++ .../cosmic-ui/scripts/accountsWizard.js | 318 + cosmic-client/cosmic-ui/scripts/affinity.js | 219 + cosmic-client/cosmic-ui/scripts/autoscaler.js | 1482 ++ .../cosmic-ui/scripts/cloud.core.callbacks.js | 84 + cosmic-client/cosmic-ui/scripts/cloudStack.js | 486 + .../cosmic-ui/scripts/configuration.js | 4066 +++++ cosmic-client/cosmic-ui/scripts/dashboard.js | 297 + cosmic-client/cosmic-ui/scripts/docs.js | 1229 ++ cosmic-client/cosmic-ui/scripts/domains.js | 780 + cosmic-client/cosmic-ui/scripts/events.js | 780 + .../cosmic-ui/scripts/globalSettings.js | 333 + .../cosmic-ui/scripts/installWizard.js | 359 + .../cosmic-ui/scripts/instanceWizard.js | 1101 ++ cosmic-client/cosmic-ui/scripts/instances.js | 2761 +++ .../cosmic-ui/scripts/lbStickyPolicy.js | 319 + cosmic-client/cosmic-ui/scripts/metrics.js | 1131 ++ cosmic-client/cosmic-ui/scripts/network.js | 6683 +++++++ cosmic-client/cosmic-ui/scripts/plugins.js | 170 + cosmic-client/cosmic-ui/scripts/projects.js | 1321 ++ cosmic-client/cosmic-ui/scripts/regions.js | 1034 ++ .../cosmic-ui/scripts/sharedFunctions.js | 2314 +++ cosmic-client/cosmic-ui/scripts/storage.js | 2428 +++ cosmic-client/cosmic-ui/scripts/system.js | 14900 ++++++++++++++++ cosmic-client/cosmic-ui/scripts/templates.js | 2626 +++ .../scripts/ui-custom/accountsWizard.js | 337 + .../cosmic-ui/scripts/ui-custom/affinity.js | 174 + .../cosmic-ui/scripts/ui-custom/autoscaler.js | 421 + .../cosmic-ui/scripts/ui-custom/dashboard.js | 205 + .../scripts/ui-custom/enableStaticNAT.js | 176 + .../scripts/ui-custom/granularSettings.js | 57 + .../scripts/ui-custom/healthCheck.js | 383 + .../scripts/ui-custom/installWizard.js | 928 + .../scripts/ui-custom/instanceWizard.js | 1354 ++ .../cosmic-ui/scripts/ui-custom/ipRules.js | 163 + .../cosmic-ui/scripts/ui-custom/login.js | 217 + .../scripts/ui-custom/metricsView.js | 209 + .../scripts/ui-custom/physicalResources.js | 270 + .../scripts/ui-custom/pluginListing.js | 129 + .../scripts/ui-custom/projectSelect.js | 77 + .../cosmic-ui/scripts/ui-custom/projects.js | 796 + .../scripts/ui-custom/recurringSnapshots.js | 215 + .../cosmic-ui/scripts/ui-custom/regions.js | 128 + .../cosmic-ui/scripts/ui-custom/saml.js | 96 + .../scripts/ui-custom/securityRules.js | 73 + .../scripts/ui-custom/uploadVolume.js | 176 + .../cosmic-ui/scripts/ui-custom/vpc.js | 940 + .../cosmic-ui/scripts/ui-custom/zoneChart.js | 453 + .../cosmic-ui/scripts/ui-custom/zoneFilter.js | 38 + .../cosmic-ui/scripts/ui-custom/zoneWizard.js | 1316 ++ cosmic-client/cosmic-ui/scripts/ui/core.js | 490 + cosmic-client/cosmic-ui/scripts/ui/dialog.js | 1019 ++ cosmic-client/cosmic-ui/scripts/ui/events.js | 62 + cosmic-client/cosmic-ui/scripts/ui/utils.js | 148 + .../scripts/ui/widgets/cloudBrowser.js | 357 + .../cosmic-ui/scripts/ui/widgets/dataTable.js | 336 + .../scripts/ui/widgets/detailView.js | 1669 ++ .../cosmic-ui/scripts/ui/widgets/listView.js | 2533 +++ .../cosmic-ui/scripts/ui/widgets/multiEdit.js | 1255 ++ .../scripts/ui/widgets/notifications.js | 385 + .../cosmic-ui/scripts/ui/widgets/overlay.js | 50 + .../cosmic-ui/scripts/ui/widgets/tagger.js | 255 + .../cosmic-ui/scripts/ui/widgets/toolTip.js | 170 + .../cosmic-ui/scripts/ui/widgets/treeView.js | 172 + .../cosmic-ui/scripts/vm_snapshots.js | 198 + cosmic-client/cosmic-ui/scripts/vpc.js | 4378 +++++ cosmic-client/cosmic-ui/scripts/zoneWizard.js | 3877 ++++ .../rc.d/init.d/cloud-management.in | 107 + .../SYSCONFDIR/sysconfig/cloud-management.in | 23 + .../rc.d/init.d/cloud-management.in | 106 + .../SYSCONFDIR/sysconfig/cloud-management.in | 23 + .../SYSCONFDIR/init.d/cloud-management.in | 239 + .../SYSCONFDIR/sysconfig/cloud-management.in | 23 + .../rc.d/init.d/cloud-management.in | 108 + .../SYSCONFDIR/sysconfig/cloud-management.in | 23 + .../SYSCONFDIR/init.d/cloud-management.in | 239 + .../SYSCONFDIR/sysconfig/cloud-management.in | 23 + .../SYSCONFDIR/init.d/cloud-management.in | 244 + cosmic-client/pom.xml | 555 + .../META-INF/cosmic/webApplicationContext.xml | 32 + cosmic-client/tomcatconf/catalina.policy.in | 180 + .../tomcatconf/catalina.properties.in | 81 + cosmic-client/tomcatconf/classpath.conf.in | 41 + .../tomcatconf/cloudmanagementserver.keystore | Bin 0 -> 1316 bytes .../tomcatconf/commands.properties.in | 720 + .../tomcatconf/commons-logging.properties.in | 30 + cosmic-client/tomcatconf/context.xml.in | 35 + cosmic-client/tomcatconf/db.properties.in | 89 + cosmic-client/tomcatconf/ehcache.xml.in | 545 + .../tomcatconf/environment.properties.in | 22 + .../tomcatconf/java.security.ciphers.in | 18 + cosmic-client/tomcatconf/log4j-cloud.xml.in | 173 + .../tomcatconf/logging.properties.in | 64 + cosmic-client/tomcatconf/server-nonssl.xml.in | 149 + cosmic-client/tomcatconf/server-ssl.xml.in | 157 + .../tomcatconf/server7-nonssl.xml.in | 149 + cosmic-client/tomcatconf/server7-ssl.xml.in | 157 + cosmic-client/tomcatconf/tomcat-users.xml.in | 31 + .../tomcatconf/tomcat6-nonssl.conf.in | 73 + cosmic-client/tomcatconf/tomcat6-ssl.conf.in | 72 + cosmic-client/tomcatconf/web.xml.in | 1188 ++ cosmic-core/LICENSE | 661 + cosmic-core/NOTICE | 79 + cosmic-core/README.md | 2 + cosmic-core/api/pom.xml | 51 + .../main/java/com/cloud/agent/api/Answer.java | 84 + .../java/com/cloud/agent/api/Command.java | 96 + .../agent/api/HostVmStateReportEntry.java | 50 + .../java/com/cloud/agent/api/LogLevel.java | 49 + .../cloud/agent/api/PvlanSetupCommand.java | 119 + .../com/cloud/agent/api/StoragePoolInfo.java | 89 + .../cloud/agent/api/UnsupportedAnswer.java | 27 + .../com/cloud/agent/api/VgpuTypesInfo.java | 86 + .../CopyTemplateToPrimaryStorageAnswer.java | 18 + .../api/storage/CreateVolumeOVAAnswer.java | 26 + .../api/storage/CreateVolumeOVACommand.java | 60 + .../cloud/agent/api/storage/PasswordAuth.java | 43 + .../api/storage/PrepareOVAPackingAnswer.java | 26 + .../api/storage/PrepareOVAPackingCommand.java | 46 + .../cloud/agent/api/to/DataObjectType.java | 23 + .../com/cloud/agent/api/to/DataStoreTO.java | 31 + .../java/com/cloud/agent/api/to/DataTO.java | 36 + .../java/com/cloud/agent/api/to/DhcpTO.java | 65 + .../java/com/cloud/agent/api/to/DiskTO.java | 96 + .../cloud/agent/api/to/FirewallRuleTO.java | 279 + .../com/cloud/agent/api/to/GPUDeviceTO.java | 59 + .../java/com/cloud/agent/api/to/HostTO.java | 84 + .../com/cloud/agent/api/to/IpAddressTO.java | 136 + .../cloud/agent/api/to/LoadBalancerTO.java | 584 + .../cloud/agent/api/to/MonitorServiceTO.java | 71 + .../com/cloud/agent/api/to/NetworkACLTO.java | 153 + .../com/cloud/agent/api/to/NetworkTO.java | 184 + .../java/com/cloud/agent/api/to/NfsTO.java | 74 + .../java/com/cloud/agent/api/to/NicTO.java | 91 + .../agent/api/to/PortForwardingRuleTO.java | 61 + .../java/com/cloud/agent/api/to/S3TO.java | 337 + .../cloud/agent/api/to/StaticNatRuleTO.java | 82 + .../cloud/agent/api/to/StorageFilerTO.java | 76 + .../java/com/cloud/agent/api/to/SwiftTO.java | 85 + .../com/cloud/agent/api/to/TemplateTO.java | 55 + .../cloud/agent/api/to/VirtualMachineTO.java | 334 + .../java/com/cloud/agent/api/to/VolumeTO.java | 197 + .../manager/allocator/HostAllocator.java | 107 + .../agent/manager/allocator/PodAllocator.java | 33 + .../src/main/java/com/cloud/alert/Alert.java | 44 + .../java/com/cloud/alert/AlertAdapter.java | 22 + .../ListRecurringSnapshotScheduleCmd.java | 79 + .../java/com/cloud/capacity/Capacity.java | 52 + .../com/cloud/capacity/CapacityState.java | 23 + .../configuration/ConfigurationService.java | 272 + .../com/cloud/configuration/Resource.java | 98 + .../cloud/configuration/ResourceCount.java | 27 + .../cloud/configuration/ResourceLimit.java | 27 + .../consoleproxy/ConsoleProxyAllocator.java | 34 + .../main/java/com/cloud/dc/DataCenter.java | 85 + .../java/com/cloud/dc/DedicatedResources.java | 43 + .../api/src/main/java/com/cloud/dc/Pod.java | 47 + .../com/cloud/dc/StorageNetworkIpRange.java | 40 + .../api/src/main/java/com/cloud/dc/Vlan.java | 57 + .../cloud/deploy/DataCenterDeployment.java | 96 + .../com/cloud/deploy/DeployDestination.java | 157 + .../deploy/DeploymentClusterPlanner.java | 66 + .../java/com/cloud/deploy/DeploymentPlan.java | 68 + .../com/cloud/deploy/DeploymentPlanner.java | 305 + .../main/java/com/cloud/deploy/HAPlanner.java | 21 + .../main/java/com/cloud/domain/Domain.java | 63 + .../main/java/com/cloud/domain/PartOf.java | 26 + .../java/com/cloud/event/ActionEvent.java | 36 + .../java/com/cloud/event/ActionEvents.java | 32 + .../src/main/java/com/cloud/event/Event.java | 49 + .../java/com/cloud/event/EventCategory.java | 56 + .../main/java/com/cloud/event/EventTypes.java | 857 + .../main/java/com/cloud/event/UsageEvent.java | 39 + .../exception/AccountLimitException.java | 39 + .../exception/AffinityConflictException.java | 34 + .../AgentControlChannelException.java | 25 + .../exception/AgentUnavailableException.java | 41 + .../CloudAuthenticationException.java | 32 + .../com/cloud/exception/CloudException.java | 68 + .../ConcurrentOperationException.java | 29 + .../ConflictingNetworkSettingsException.java | 28 + .../cloud/exception/ConnectionException.java | 52 + .../DiscoveredWithErrorException.java | 32 + .../cloud/exception/DiscoveryException.java | 32 + .../com/cloud/exception/HAStateException.java | 32 + .../IllegalVirtualMachineException.java | 12 + .../InsufficientAddressCapacityException.java | 36 + .../InsufficientCapacityException.java | 68 + .../InsufficientNetworkCapacityException.java | 31 + .../InsufficientServerCapacityException.java | 48 + .../InsufficientStorageCapacityException.java | 37 + ...icientVirtualNetworkCapacityException.java | 32 + .../exception/InternalErrorException.java | 31 + .../exception/ManagementServerException.java | 37 + .../MissingParameterValueException.java | 26 + .../NetworkRuleConflictException.java | 27 + .../exception/NicPreparationException.java | 14 + .../exception/OperationTimedoutException.java | 70 + .../exception/PermissionDeniedException.java | 68 + .../exception/RequestLimitException.java | 41 + .../ResourceAllocationException.java | 35 + .../exception/ResourceInUseException.java | 50 + .../ResourceUnavailableException.java | 44 + .../exception/StorageConflictException.java | 27 + .../StorageUnavailableException.java | 45 + .../UnsupportedServiceException.java | 26 + .../VirtualMachineMigrationException.java | 27 + .../api/src/main/java/com/cloud/gpu/GPU.java | 47 + .../main/java/com/cloud/ha/FenceBuilder.java | 31 + .../main/java/com/cloud/ha/Investigator.java | 42 + .../src/main/java/com/cloud/host/Host.java | 205 + .../java/com/cloud/host/HostEnvironment.java | 51 + .../main/java/com/cloud/host/HostStats.java | 40 + .../src/main/java/com/cloud/host/Status.java | 171 + .../java/com/cloud/hypervisor/Hypervisor.java | 72 + .../hypervisor/HypervisorCapabilities.java | 55 + .../com/cloud/hypervisor/HypervisorGuru.java | 84 + .../com/cloud/info/ConsoleProxyLoadInfo.java | 48 + .../com/cloud/info/RunningHostCountInfo.java | 48 + .../java/com/cloud/network/GuestVlan.java | 32 + .../java/com/cloud/network/IpAddress.java | 95 + .../com/cloud/network/MonitoringService.java | 45 + .../main/java/com/cloud/network/Network.java | 363 + .../network/NetworkMigrationResponder.java | 69 + .../java/com/cloud/network/NetworkModel.java | 284 + .../com/cloud/network/NetworkProfile.java | 303 + .../com/cloud/network/NetworkRuleApplier.java | 28 + .../com/cloud/network/NetworkService.java | 181 + .../cloud/network/NetworkUsageService.java | 36 + .../main/java/com/cloud/network/Networks.java | 356 + .../com/cloud/network/PhysicalNetwork.java | 65 + .../PhysicalNetworkServiceProvider.java | 70 + .../network/PhysicalNetworkSetupInfo.java | 84 + .../network/PhysicalNetworkTrafficType.java | 36 + .../com/cloud/network/PublicIpAddress.java | 33 + .../com/cloud/network/RemoteAccessVpn.java | 45 + .../network/Site2SiteCustomerGateway.java | 47 + .../cloud/network/Site2SiteVpnConnection.java | 49 + .../cloud/network/Site2SiteVpnGateway.java | 36 + .../cloud/network/StorageNetworkService.java | 37 + .../java/com/cloud/network/TrafficLabel.java | 35 + .../com/cloud/network/UserIpv6Address.java | 52 + .../VirtualNetworkApplianceService.java | 71 + .../cloud/network/VirtualRouterProvider.java | 32 + .../VpcVirtualNetworkApplianceService.java | 52 + .../main/java/com/cloud/network/VpnUser.java | 33 + .../cloud/network/as/AutoScaleCounter.java | 122 + .../com/cloud/network/as/AutoScalePolicy.java | 40 + .../cloud/network/as/AutoScaleService.java | 81 + .../cloud/network/as/AutoScaleVmGroup.java | 60 + .../cloud/network/as/AutoScaleVmProfile.java | 55 + .../java/com/cloud/network/as/Condition.java | 35 + .../java/com/cloud/network/as/Counter.java | 37 + .../element/AggregatedCommandExecutor.java | 28 + .../network/element/ConnectivityProvider.java | 21 + .../network/element/DhcpServiceProvider.java | 36 + .../element/FirewallServiceProvider.java | 34 + .../com/cloud/network/element/IpDeployer.java | 44 + .../network/element/IpDeployingRequester.java | 28 + .../element/LoadBalancingServiceProvider.java | 49 + .../element/NetworkACLServiceProvider.java | 35 + .../cloud/network/element/NetworkElement.java | 122 + .../PortForwardingServiceProvider.java | 34 + .../RemoteAccessVPNServiceProvider.java | 32 + .../element/Site2SiteVpnServiceProvider.java | 27 + .../element/SourceNatServiceProvider.java | 20 + .../element/StaticNatServiceProvider.java | 34 + .../element/UserDataServiceProvider.java | 37 + .../element/VirtualRouterElementService.java | 36 + .../cloud/network/element/VpcProvider.java | 55 + .../network/firewall/FirewallService.java | 57 + .../com/cloud/network/guru/NetworkGuru.java | 211 + .../com/cloud/network/lb/CertService.java | 33 + .../cloud/network/lb/LoadBalancingRule.java | 485 + .../network/lb/LoadBalancingRulesService.java | 166 + .../java/com/cloud/network/lb/SslCert.java | 35 + .../cloud/network/router/VirtualRouter.java | 51 + .../com/cloud/network/rules/FirewallRule.java | 94 + .../network/rules/HealthCheckPolicy.java | 46 + .../network/rules/LbStickinessMethod.java | 139 + .../com/cloud/network/rules/LoadBalancer.java | 28 + .../network/rules/LoadBalancerContainer.java | 35 + .../network/rules/PortForwardingRule.java | 50 + .../com/cloud/network/rules/RulesService.java | 86 + .../com/cloud/network/rules/StaticNat.java | 34 + .../cloud/network/rules/StaticNatRule.java | 22 + .../cloud/network/rules/StickinessPolicy.java | 46 + .../cloud/network/security/SecurityGroup.java | 30 + .../network/security/SecurityGroupRules.java | 48 + .../security/SecurityGroupService.java | 51 + .../cloud/network/security/SecurityRule.java | 54 + .../com/cloud/network/vpc/NetworkACL.java | 41 + .../com/cloud/network/vpc/NetworkACLItem.java | 81 + .../cloud/network/vpc/NetworkACLService.java | 136 + .../com/cloud/network/vpc/PrivateGateway.java | 21 + .../java/com/cloud/network/vpc/PrivateIp.java | 50 + .../com/cloud/network/vpc/StaticRoute.java | 48 + .../cloud/network/vpc/StaticRouteProfile.java | 88 + .../main/java/com/cloud/network/vpc/Vpc.java | 90 + .../com/cloud/network/vpc/VpcGateway.java | 86 + .../com/cloud/network/vpc/VpcOffering.java | 70 + .../network/vpc/VpcProvisioningService.java | 52 + .../com/cloud/network/vpc/VpcService.java | 259 + .../network/vpn/RemoteAccessVpnService.java | 60 + .../network/vpn/Site2SiteVpnService.java | 70 + .../java/com/cloud/offering/DiskOffering.java | 123 + .../com/cloud/offering/DiskOfferingInfo.java | 65 + .../com/cloud/offering/NetworkOffering.java | 131 + .../com/cloud/offering/OfferingManager.java | 48 + .../com/cloud/offering/ServiceOffering.java | 121 + .../src/main/java/com/cloud/org/Cluster.java | 43 + .../src/main/java/com/cloud/org/Grouping.java | 24 + .../src/main/java/com/cloud/org/Managed.java | 24 + .../main/java/com/cloud/org/RunningIn.java | 27 + .../main/java/com/cloud/projects/Project.java | 55 + .../com/cloud/projects/ProjectAccount.java | 31 + .../com/cloud/projects/ProjectInvitation.java | 44 + .../com/cloud/projects/ProjectService.java | 87 + .../region/ha/GlobalLoadBalancerRule.java | 86 + .../ha/GlobalLoadBalancingRulesService.java | 57 + .../com/cloud/resource/ResourceService.java | 99 + .../com/cloud/resource/ResourceState.java | 113 + .../resource/UnableDeleteHostException.java | 28 + .../main/java/com/cloud/serializer/Param.java | 42 + .../com/cloud/server/ManagementService.java | 439 + .../cloud/server/ResourceMetaDataService.java | 63 + .../java/com/cloud/server/ResourceTag.java | 112 + .../cloud/server/TaggedResourceService.java | 57 + .../storage/DataStoreProviderApiService.java | 28 + .../java/com/cloud/storage/DataStoreRole.java | 52 + .../main/java/com/cloud/storage/GuestOS.java | 37 + .../com/cloud/storage/GuestOSHypervisor.java | 40 + .../com/cloud/storage/GuestOsCategory.java | 30 + .../java/com/cloud/storage/ImageStore.java | 44 + .../java/com/cloud/storage/ScopeType.java | 23 + .../main/java/com/cloud/storage/Snapshot.java | 92 + .../main/java/com/cloud/storage/Storage.java | 162 + .../java/com/cloud/storage/StorageGuru.java | 31 + .../java/com/cloud/storage/StoragePool.java | 107 + .../cloud/storage/StoragePoolDiscoverer.java | 34 + .../com/cloud/storage/StoragePoolStatus.java | 21 + .../com/cloud/storage/StorageService.java | 112 + .../java/com/cloud/storage/StorageStats.java | 29 + .../main/java/com/cloud/storage/Upload.java | 80 + .../VMTemplateStorageResourceAssoc.java | 64 + .../main/java/com/cloud/storage/Volume.java | 227 + .../com/cloud/storage/VolumeApiService.java | 107 + .../java/com/cloud/storage/VolumeStats.java | 24 + .../storage/snapshot/SnapshotApiService.java | 112 + .../storage/snapshot/SnapshotPolicy.java | 46 + .../storage/snapshot/SnapshotSchedule.java | 46 + .../cloud/storage/template/TemplateProp.java | 81 + .../main/java/com/cloud/template/BasedOn.java | 29 + .../cloud/template/TemplateApiService.java | 106 + .../template/VirtualMachineTemplate.java | 139 + .../src/main/java/com/cloud/user/Account.java | 62 + .../java/com/cloud/user/AccountService.java | 139 + .../java/com/cloud/user/DomainService.java | 59 + .../src/main/java/com/cloud/user/OwnedBy.java | 26 + .../com/cloud/user/ResourceLimitService.java | 184 + .../main/java/com/cloud/user/SSHKeyPair.java | 44 + .../src/main/java/com/cloud/user/User.java | 92 + .../main/java/com/cloud/user/UserAccount.java | 70 + .../main/java/com/cloud/uservm/UserVm.java | 43 + .../main/java/com/cloud/vm/ConsoleProxy.java | 27 + .../main/java/com/cloud/vm/DiskProfile.java | 221 + .../main/java/com/cloud/vm/InstanceGroup.java | 33 + .../api/src/main/java/com/cloud/vm/Nic.java | 163 + .../main/java/com/cloud/vm/NicIpAlias.java | 53 + .../main/java/com/cloud/vm/NicProfile.java | 423 + .../java/com/cloud/vm/NicSecondaryIp.java | 40 + .../java/com/cloud/vm/ReservationContext.java | 53 + .../src/main/java/com/cloud/vm/RunningOn.java | 26 + .../java/com/cloud/vm/SecondaryStorageVm.java | 27 + .../src/main/java/com/cloud/vm/SystemVm.java | 29 + .../main/java/com/cloud/vm/UserVmService.java | 485 + .../java/com/cloud/vm/VirtualMachine.java | 327 + .../java/com/cloud/vm/VirtualMachineName.java | 187 + .../com/cloud/vm/VirtualMachineProfile.java | 174 + .../java/com/cloud/vm/VmDetailConstants.java | 26 + .../main/java/com/cloud/vm/VmDiskStats.java | 30 + .../src/main/java/com/cloud/vm/VmStats.java | 35 + .../com/cloud/vm/snapshot/VMSnapshot.java | 105 + .../cloud/vm/snapshot/VMSnapshotService.java | 48 + .../org/apache/cloudstack/acl/APIChecker.java | 30 + .../cloudstack/acl/APILimitChecker.java | 30 + .../cloudstack/acl/ControlledEntity.java | 33 + .../cloudstack/acl/InfrastructureEntity.java | 20 + .../cloudstack/acl/PermissionScope.java | 41 + .../apache/cloudstack/acl/QuerySelector.java | 74 + .../org/apache/cloudstack/acl/RoleType.java | 33 + .../cloudstack/acl/SecurityChecker.java | 142 + .../cloudstack/affinity/AffinityGroup.java | 33 + .../affinity/AffinityGroupProcessor.java | 88 + .../affinity/AffinityGroupResponse.java | 164 + .../affinity/AffinityGroupService.java | 70 + .../affinity/AffinityGroupTypeResponse.java | 41 + .../affinity/AffinityProcessorBase.java | 69 + .../apache/cloudstack/alert/AlertService.java | 104 + .../java/org/apache/cloudstack/api/ACL.java | 38 + .../org/apache/cloudstack/api/APICommand.java | 52 + .../api/AbstractGetUploadParamsCmd.java | 90 + .../cloudstack/api/ApiCommandJobType.java | 56 + .../apache/cloudstack/api/ApiConstants.java | 612 + .../apache/cloudstack/api/ApiErrorCode.java | 60 + .../cloudstack/api/ApiServerService.java | 45 + .../apache/cloudstack/api/BaseAsyncCmd.java | 104 + .../cloudstack/api/BaseAsyncCreateCmd.java | 52 + .../api/BaseAsyncCreateCustomIdCmd.java | 28 + .../cloudstack/api/BaseAsyncCustomIdCmd.java | 33 + .../org/apache/cloudstack/api/BaseCmd.java | 411 + .../cloudstack/api/BaseCustomIdCmd.java | 34 + .../api/BaseListAccountResourcesCmd.java | 34 + .../apache/cloudstack/api/BaseListCmd.java | 145 + .../api/BaseListDomainResourcesCmd.java | 51 + ...BaseListProjectAndAccountResourcesCmd.java | 30 + .../api/BaseListTaggedResourcesCmd.java | 49 + .../BaseListTemplateOrIsoPermissionsCmd.java | 89 + .../apache/cloudstack/api/BaseResponse.java | 78 + .../api/BaseUpdateTemplateOrIsoCmd.java | 132 + ...BaseUpdateTemplateOrIsoPermissionsCmd.java | 133 + .../apache/cloudstack/api/Displayable.java | 21 + .../cloudstack/api/EntityReference.java | 28 + .../api/IBaseListAccountResourcesCmd.java | 24 + .../apache/cloudstack/api/IBaseListCmd.java | 32 + .../api/IBaseListDomainResourcesCmd.java | 26 + ...BaseListProjectAndAccountResourcesCmd.java | 22 + .../api/IBaseListTaggedResourcesCmd.java | 24 + .../org/apache/cloudstack/api/Identity.java | 21 + .../cloudstack/api/InternalIdentity.java | 28 + .../apache/cloudstack/api/LdapValidator.java | 21 + .../org/apache/cloudstack/api/Parameter.java | 52 + .../apache/cloudstack/api/ResourceDetail.java | 29 + .../cloudstack/api/ResponseGenerator.java | 448 + .../apache/cloudstack/api/ResponseObject.java | 83 + .../cloudstack/api/ServerApiException.java | 89 + .../org/apache/cloudstack/api/Validate.java | 31 + .../api/auth/APIAuthenticationManager.java | 24 + .../api/auth/APIAuthenticationType.java | 21 + .../cloudstack/api/auth/APIAuthenticator.java | 47 + .../api/auth/PluggableAPIAuthenticator.java | 25 + .../admin/account/CreateAccountCmd.java | 200 + .../admin/account/DeleteAccountCmd.java | 122 + .../admin/account/DisableAccountCmd.java | 139 + .../admin/account/EnableAccountCmd.java | 111 + .../admin/account/ListAccountsCmdByAdmin.java | 29 + .../command/admin/account/LockAccountCmd.java | 91 + .../admin/account/UpdateAccountCmd.java | 142 + .../address/AssociateIPAddrCmdByAdmin.java | 66 + .../ListPublicIpAddressesCmdByAdmin.java | 54 + .../UpdateVMAffinityGroupCmdByAdmin.java | 67 + .../command/admin/alert/GenerateAlertCmd.java | 122 + .../admin/autoscale/CreateCounterCmd.java | 119 + .../admin/autoscale/DeleteCounterCmd.java | 104 + .../command/admin/cluster/AddClusterCmd.java | 223 + .../admin/cluster/DeleteClusterCmd.java | 78 + .../admin/cluster/ListClustersCmd.java | 143 + .../admin/cluster/UpdateClusterCmd.java | 119 + .../command/admin/config/ListCfgsByCmd.java | 157 + .../config/ListDeploymentPlannersCmd.java | 71 + .../config/ListHypervisorCapabilitiesCmd.java | 97 + .../command/admin/config/UpdateCfgCmd.java | 142 + .../UpdateHypervisorCapabilitiesCmd.java | 96 + .../command/admin/domain/CreateDomainCmd.java | 105 + .../command/admin/domain/DeleteDomainCmd.java | 111 + .../admin/domain/ListDomainChildrenCmd.java | 105 + .../command/admin/domain/ListDomainsCmd.java | 91 + .../admin/domain/ListDomainsCmdByAdmin.java | 28 + .../command/admin/domain/UpdateDomainCmd.java | 103 + .../command/admin/guest/AddGuestOsCmd.java | 136 + .../admin/guest/AddGuestOsMappingCmd.java | 146 + .../admin/guest/ListGuestOsMappingCmd.java | 101 + .../command/admin/guest/RemoveGuestOsCmd.java | 98 + .../admin/guest/RemoveGuestOsMappingCmd.java | 98 + .../command/admin/guest/UpdateGuestOsCmd.java | 104 + .../admin/guest/UpdateGuestOsMappingCmd.java | 102 + .../api/command/admin/host/AddHostCmd.java | 163 + .../admin/host/AddSecondaryStorageCmd.java | 94 + .../admin/host/CancelMaintenanceCmd.java | 111 + .../api/command/admin/host/DeleteHostCmd.java | 96 + .../admin/host/FindHostsForMigrationCmd.java | 109 + .../command/admin/host/ListHostTagsCmd.java | 65 + .../api/command/admin/host/ListHostsCmd.java | 212 + .../admin/host/PrepareForMaintenanceCmd.java | 111 + .../command/admin/host/ReconnectHostCmd.java | 116 + .../admin/host/ReleaseHostReservationCmd.java | 106 + .../api/command/admin/host/UpdateHostCmd.java | 120 + .../admin/host/UpdateHostPasswordCmd.java | 108 + ...nfigureInternalLoadBalancerElementCmd.java | 118 + .../CreateInternalLoadBalancerElementCmd.java | 124 + .../internallb/ListInternalLBVMsCmd.java | 140 + .../ListInternalLoadBalancerElementsCmd.java | 109 + .../internallb/StartInternalLBVMCmd.java | 125 + .../admin/internallb/StopInternalLBVMCmd.java | 127 + .../admin/iso/AttachIsoCmdByAdmin.java | 54 + .../command/admin/iso/CopyIsoCmdByAdmin.java | 28 + .../admin/iso/DetachIsoCmdByAdmin.java | 48 + .../iso/ListIsoPermissionsCmdByAdmin.java | 33 + .../command/admin/iso/ListIsosCmdByAdmin.java | 27 + .../admin/iso/RegisterIsoCmdByAdmin.java | 54 + .../admin/iso/UpdateIsoCmdByAdmin.java | 46 + ...stLoadBalancerRuleInstancesCmdByAdmin.java | 94 + .../admin/network/AddNetworkDeviceCmd.java | 90 + .../network/AddNetworkServiceProviderCmd.java | 149 + .../network/CreateNetworkCmdByAdmin.java | 67 + .../network/CreateNetworkOfferingCmd.java | 304 + .../network/CreatePhysicalNetworkCmd.java | 187 + .../CreateStorageNetworkIpRangeCmd.java | 138 + .../network/DedicateGuestVlanRangeCmd.java | 124 + .../admin/network/DeleteNetworkDeviceCmd.java | 91 + .../network/DeleteNetworkOfferingCmd.java | 81 + .../DeleteNetworkServiceProviderCmd.java | 109 + .../network/DeletePhysicalNetworkCmd.java | 100 + .../DeleteStorageNetworkIpRangeCmd.java | 96 + .../ListDedicatedGuestVlanRangesCmd.java | 141 + .../admin/network/ListNetworkDeviceCmd.java | 91 + .../ListNetworkIsolationMethodsCmd.java | 62 + .../ListNetworkServiceProvidersCmd.java | 108 + .../admin/network/ListNetworksCmdByAdmin.java | 51 + .../network/ListPhysicalNetworksCmd.java | 106 + .../network/ListStorageNetworkIpRangeCmd.java | 117 + .../ListSupportedNetworkServicesCmd.java | 118 + .../ReleaseDedicatedGuestVlanRangeCmd.java | 102 + .../network/UpdateNetworkCmdByAdmin.java | 64 + .../network/UpdateNetworkOfferingCmd.java | 132 + .../UpdateNetworkServiceProviderCmd.java | 119 + .../network/UpdatePhysicalNetworkCmd.java | 121 + .../UpdateStorageNetworkIpRangeCmd.java | 127 + .../admin/offering/CreateDiskOfferingCmd.java | 204 + .../offering/CreateServiceOfferingCmd.java | 289 + .../admin/offering/DeleteDiskOfferingCmd.java | 77 + .../offering/DeleteServiceOfferingCmd.java | 81 + .../admin/offering/UpdateDiskOfferingCmd.java | 111 + .../offering/UpdateServiceOfferingCmd.java | 104 + .../api/command/admin/pod/CreatePodCmd.java | 125 + .../api/command/admin/pod/DeletePodCmd.java | 78 + .../api/command/admin/pod/ListPodsByCmd.java | 109 + .../api/command/admin/pod/UpdatePodCmd.java | 121 + .../command/admin/region/AddRegionCmd.java | 98 + .../region/CreatePortableIpRangeCmd.java | 154 + .../region/DeletePortableIpRangeCmd.java | 95 + .../admin/region/ListPortableIpRangesCmd.java | 106 + .../command/admin/region/RemoveRegionCmd.java | 82 + .../command/admin/region/UpdateRegionCmd.java | 98 + .../admin/resource/ArchiveAlertsCmd.java | 115 + .../resource/CleanVMReservationsCmd.java | 81 + .../admin/resource/DeleteAlertsCmd.java | 115 + .../command/admin/resource/ListAlertsCmd.java | 101 + .../admin/resource/ListCapacityCmd.java | 134 + .../resource/UploadCustomCertificateCmd.java | 121 + .../ConfigureVirtualRouterElementCmd.java | 138 + .../router/CreateVirtualRouterElementCmd.java | 142 + .../admin/router/DestroyRouterCmd.java | 112 + .../command/admin/router/ListRoutersCmd.java | 155 + .../router/ListVirtualRouterElementsCmd.java | 116 + .../command/admin/router/RebootRouterCmd.java | 111 + .../command/admin/router/StartRouterCmd.java | 123 + .../command/admin/router/StopRouterCmd.java | 126 + .../admin/router/UpgradeRouterCmd.java | 96 + .../router/UpgradeRouterTemplateCmd.java | 141 + .../admin/storage/AddImageStoreCmd.java | 150 + .../admin/storage/AddImageStoreS3CMD.java | 277 + .../CancelPrimaryStorageMaintenanceCmd.java | 112 + .../CreateSecondaryStagingStoreCmd.java | 126 + .../admin/storage/CreateStoragePoolCmd.java | 184 + .../admin/storage/DeleteImageStoreCmd.java | 78 + .../command/admin/storage/DeletePoolCmd.java | 93 + .../DeleteSecondaryStagingStoreCmd.java | 78 + .../FindStoragePoolsForMigrationCmd.java | 98 + .../admin/storage/ListImageStoresCmd.java | 98 + .../ListSecondaryStagingStoresCmd.java | 98 + .../admin/storage/ListStoragePoolsCmd.java | 126 + .../storage/ListStorageProvidersCmd.java | 74 + .../admin/storage/ListStorageTagsCmd.java | 65 + ...reparePrimaryStorageForMaintenanceCmd.java | 112 + .../UpdateCloudToUseObjectStoreCmd.java | 143 + .../admin/storage/UpdateStoragePoolCmd.java | 110 + .../api/command/admin/swift/AddSwiftCmd.java | 115 + .../command/admin/swift/ListSwiftsCmd.java | 71 + .../admin/systemvm/DestroySystemVmCmd.java | 112 + .../admin/systemvm/ListSystemVMsCmd.java | 145 + .../admin/systemvm/MigrateSystemVMCmd.java | 143 + .../admin/systemvm/RebootSystemVmCmd.java | 119 + .../admin/systemvm/ScaleSystemVMCmd.java | 145 + .../admin/systemvm/StartSystemVMCmd.java | 123 + .../admin/systemvm/StopSystemVmCmd.java | 129 + .../admin/systemvm/UpgradeSystemVMCmd.java | 115 + .../template/CopyTemplateCmdByAdmin.java | 64 + .../template/CreateTemplateCmdByAdmin.java | 57 + .../ListTemplatePermissionsCmdByAdmin.java | 34 + .../template/ListTemplatesCmdByAdmin.java | 30 + .../admin/template/PrepareTemplateCmd.java | 113 + .../template/RegisterTemplateCmdByAdmin.java | 58 + .../template/UpdateTemplateCmdByAdmin.java | 48 + .../admin/usage/AddTrafficMonitorCmd.java | 109 + .../admin/usage/AddTrafficTypeCmd.java | 174 + .../admin/usage/DeleteTrafficMonitorCmd.java | 83 + .../admin/usage/DeleteTrafficTypeCmd.java | 95 + .../admin/usage/GenerateUsageRecordsCmd.java | 103 + .../admin/usage/GetUsageRecordsCmd.java | 165 + .../admin/usage/ListTrafficMonitorsCmd.java | 82 + .../usage/ListTrafficTypeImplementorsCmd.java | 94 + .../admin/usage/ListTrafficTypesCmd.java | 93 + .../admin/usage/ListUsageTypesCmd.java | 54 + .../admin/usage/RemoveRawUsageRecordsCmd.java | 80 + .../admin/usage/UpdateTrafficTypeCmd.java | 123 + .../api/command/admin/user/CreateUserCmd.java | 176 + .../api/command/admin/user/DeleteUserCmd.java | 91 + .../command/admin/user/DisableUserCmd.java | 110 + .../api/command/admin/user/EnableUserCmd.java | 93 + .../api/command/admin/user/GetUserCmd.java | 78 + .../api/command/admin/user/ListUsersCmd.java | 88 + .../api/command/admin/user/LockUserCmd.java | 85 + .../api/command/admin/user/RegisterCmd.java | 90 + .../api/command/admin/user/UpdateUserCmd.java | 154 + .../admin/vlan/CreateVlanIpRangeCmd.java | 235 + .../admin/vlan/DedicatePublicIpRangeCmd.java | 109 + .../admin/vlan/DeleteVlanIpRangeCmd.java | 78 + .../admin/vlan/ListVlanIpRangesCmd.java | 158 + .../admin/vlan/ReleasePublicIpRangeCmd.java | 78 + .../admin/vm/AddNicToVMCmdByAdmin.java | 57 + .../api/command/admin/vm/AssignVMCmd.java | 145 + .../command/admin/vm/DeployVMCmdByAdmin.java | 81 + .../command/admin/vm/DestroyVMCmdByAdmin.java | 60 + .../api/command/admin/vm/ExpungeVMCmd.java | 123 + .../command/admin/vm/GetVMUserDataCmd.java | 83 + .../command/admin/vm/ListVMsCmdByAdmin.java | 71 + .../api/command/admin/vm/MigrateVMCmd.java | 189 + .../MigrateVirtualMachineWithVolumeCmd.java | 177 + .../command/admin/vm/RebootVMCmdByAdmin.java | 53 + .../api/command/admin/vm/RecoverVMCmd.java | 88 + .../admin/vm/RemoveNicFromVMCmdByAdmin.java | 56 + .../admin/vm/ResetVMPasswordCmdByAdmin.java | 56 + .../admin/vm/ResetVMSSHKeyCmdByAdmin.java | 59 + .../command/admin/vm/RestoreVMCmdByAdmin.java | 57 + .../command/admin/vm/ScaleVMCmdByAdmin.java | 71 + .../command/admin/vm/StartVMCmdByAdmin.java | 82 + .../command/admin/vm/StopVMCmdByAdmin.java | 55 + .../vm/UpdateDefaultNicForVMCmdByAdmin.java | 57 + .../command/admin/vm/UpdateVMCmdByAdmin.java | 55 + .../command/admin/vm/UpgradeVMCmdByAdmin.java | 61 + .../RevertToVMSnapshotCmdByAdmin.java | 57 + .../admin/volume/AttachVolumeCmdByAdmin.java | 49 + .../admin/volume/CreateVolumeCmdByAdmin.java | 64 + .../admin/volume/DetachVolumeCmdByAdmin.java | 50 + .../admin/volume/ListVolumesCmdByAdmin.java | 65 + .../admin/volume/MigrateVolumeCmdByAdmin.java | 48 + .../admin/volume/ResizeVolumeCmdByAdmin.java | 56 + .../admin/volume/UpdateVolumeCmdByAdmin.java | 46 + .../admin/volume/UploadVolumeCmdByAdmin.java | 58 + .../admin/vpc/CreatePrivateGatewayCmd.java | 214 + .../admin/vpc/CreateVPCCmdByAdmin.java | 68 + .../admin/vpc/CreateVPCOfferingCmd.java | 174 + .../admin/vpc/DeletePrivateGatewayCmd.java | 114 + .../admin/vpc/DeleteVPCOfferingCmd.java | 89 + .../command/admin/vpc/ListVPCsCmdByAdmin.java | 57 + .../admin/vpc/UpdateVPCCmdByAdmin.java | 48 + .../admin/vpc/UpdateVPCOfferingCmd.java | 109 + .../api/command/admin/zone/CreateZoneCmd.java | 181 + .../api/command/admin/zone/DeleteZoneCmd.java | 80 + .../admin/zone/ListZonesCmdByAdmin.java | 27 + .../zone/MarkDefaultZoneForAccountCmd.java | 126 + .../api/command/admin/zone/UpdateZoneCmd.java | 192 + .../user/account/AddAccountToProjectCmd.java | 126 + .../account/DeleteAccountFromProjectCmd.java | 109 + .../command/user/account/ListAccountsCmd.java | 97 + .../user/account/ListProjectAccountsCmd.java | 90 + .../user/address/AssociateIPAddrCmd.java | 349 + .../user/address/DisassociateIPAddrCmd.java | 157 + .../address/ListPublicIpAddressesCmd.java | 200 + .../command/user/address/UpdateIPAddrCmd.java | 136 + .../affinitygroup/CreateAffinityGroupCmd.java | 177 + .../affinitygroup/DeleteAffinityGroupCmd.java | 141 + .../ListAffinityGroupTypesCmd.java | 69 + .../affinitygroup/ListAffinityGroupsCmd.java | 96 + .../UpdateVMAffinityGroupCmd.java | 171 + .../autoscale/CreateAutoScalePolicyCmd.java | 181 + .../autoscale/CreateAutoScaleVmGroupCmd.java | 233 + .../CreateAutoScaleVmProfileCmd.java | 271 + .../user/autoscale/CreateConditionCmd.java | 149 + .../autoscale/DeleteAutoScalePolicyCmd.java | 111 + .../autoscale/DeleteAutoScaleVmGroupCmd.java | 111 + .../DeleteAutoScaleVmProfileCmd.java | 110 + .../user/autoscale/DeleteConditionCmd.java | 113 + .../autoscale/DisableAutoScaleVmGroupCmd.java | 109 + .../autoscale/EnableAutoScaleVmGroupCmd.java | 109 + .../autoscale/ListAutoScalePoliciesCmd.java | 106 + .../autoscale/ListAutoScaleVmGroupsCmd.java | 128 + .../autoscale/ListAutoScaleVmProfilesCmd.java | 125 + .../user/autoscale/ListConditionsCmd.java | 100 + .../user/autoscale/ListCountersCmd.java | 99 + .../autoscale/UpdateAutoScalePolicyCmd.java | 138 + .../autoscale/UpdateAutoScaleVmGroupCmd.java | 176 + .../UpdateAutoScaleVmProfileCmd.java | 168 + .../user/config/ListCapabilitiesCmd.java | 72 + .../command/user/event/ArchiveEventsCmd.java | 121 + .../command/user/event/DeleteEventsCmd.java | 121 + .../command/user/event/ListEventTypesCmd.java | 63 + .../api/command/user/event/ListEventsCmd.java | 116 + .../firewall/CreateEgressFirewallRuleCmd.java | 365 + .../user/firewall/CreateFirewallRuleCmd.java | 357 + .../firewall/CreatePortForwardingRuleCmd.java | 451 + .../firewall/DeleteEgressFirewallRuleCmd.java | 128 + .../user/firewall/DeleteFirewallRuleCmd.java | 126 + .../firewall/DeletePortForwardingRuleCmd.java | 130 + .../user/firewall/IListFirewallRulesCmd.java | 32 + .../firewall/ListEgressFirewallRulesCmd.java | 120 + .../user/firewall/ListFirewallRulesCmd.java | 122 + .../firewall/ListPortForwardingRulesCmd.java | 115 + .../firewall/UpdateEgressFirewallRuleCmd.java | 123 + .../user/firewall/UpdateFirewallRuleCmd.java | 124 + .../firewall/UpdatePortForwardingRuleCmd.java | 157 + .../user/guest/ListGuestOsCategoriesCmd.java | 91 + .../command/user/guest/ListGuestOsCmd.java | 95 + .../api/command/user/iso/AttachIsoCmd.java | 114 + .../api/command/user/iso/CopyIsoCmd.java | 31 + .../api/command/user/iso/DeleteIsoCmd.java | 122 + .../api/command/user/iso/DetachIsoCmd.java | 99 + .../api/command/user/iso/ExtractIsoCmd.java | 147 + .../user/iso/ListIsoPermissionsCmd.java | 56 + .../api/command/user/iso/ListIsosCmd.java | 160 + .../api/command/user/iso/RegisterIsoCmd.java | 205 + .../api/command/user/iso/UpdateIsoCmd.java | 86 + .../user/iso/UpdateIsoPermissionsCmd.java | 50 + .../command/user/job/ListAsyncJobsCmd.java | 64 + .../user/job/QueryAsyncJobResultCmd.java | 71 + .../AssignCertToLoadBalancerCmd.java | 106 + .../AssignToLoadBalancerRuleCmd.java | 198 + .../CreateApplicationLoadBalancerCmd.java | 240 + .../CreateLBHealthCheckPolicyCmd.java | 205 + .../CreateLBStickinessPolicyCmd.java | 196 + .../CreateLoadBalancerRuleCmd.java | 429 + .../DeleteApplicationLoadBalancerCmd.java | 116 + .../DeleteLBHealthCheckPolicyCmd.java | 120 + .../DeleteLBStickinessPolicyCmd.java | 120 + .../DeleteLoadBalancerRuleCmd.java | 122 + .../user/loadbalancer/DeleteSslCertCmd.java | 93 + .../ListApplicationLoadBalancersCmd.java | 146 + .../ListLBHealthCheckPoliciesCmd.java | 112 + .../ListLBStickinessPoliciesCmd.java | 132 + .../ListLoadBalancerRuleInstancesCmd.java | 146 + .../ListLoadBalancerRulesCmd.java | 140 + .../user/loadbalancer/ListSslCertsCmd.java | 114 + .../RemoveCertFromLoadBalancerCmd.java | 93 + .../RemoveFromLoadBalancerRuleCmd.java | 187 + .../UpdateApplicationLoadBalancerCmd.java | 106 + .../UpdateLBHealthCheckPolicyCmd.java | 101 + .../UpdateLBStickinessPolicyCmd.java | 100 + .../UpdateLoadBalancerRuleCmd.java | 153 + .../user/loadbalancer/UploadSslCertCmd.java | 136 + .../user/nat/CreateIpForwardingRuleCmd.java | 331 + .../user/nat/DeleteIpForwardingRuleCmd.java | 124 + .../command/user/nat/DisableStaticNatCmd.java | 115 + .../command/user/nat/EnableStaticNatCmd.java | 147 + .../user/nat/ListIpForwardingRulesCmd.java | 107 + .../user/network/CreateNetworkACLCmd.java | 262 + .../user/network/CreateNetworkACLListCmd.java | 138 + .../user/network/CreateNetworkCmd.java | 313 + .../user/network/DeleteNetworkACLCmd.java | 93 + .../user/network/DeleteNetworkACLListCmd.java | 92 + .../user/network/DeleteNetworkCmd.java | 127 + .../user/network/ListNetworkACLListsCmd.java | 112 + .../user/network/ListNetworkACLsCmd.java | 128 + .../user/network/ListNetworkOfferingsCmd.java | 203 + .../command/user/network/ListNetworksCmd.java | 174 + .../network/ReplaceNetworkACLListCmd.java | 121 + .../user/network/RestartNetworkCmd.java | 133 + .../user/network/UpdateNetworkACLItemCmd.java | 198 + .../user/network/UpdateNetworkACLListCmd.java | 101 + .../user/network/UpdateNetworkCmd.java | 206 + .../user/offering/ListDiskOfferingsCmd.java | 73 + .../offering/ListServiceOfferingsCmd.java | 100 + .../user/project/ActivateProjectCmd.java | 98 + .../user/project/CreateProjectCmd.java | 147 + .../user/project/DeleteProjectCmd.java | 99 + .../project/DeleteProjectInvitationCmd.java | 90 + .../project/ListProjectInvitationsCmd.java | 91 + .../command/user/project/ListProjectsCmd.java | 118 + .../user/project/SuspendProjectCmd.java | 101 + .../user/project/UpdateProjectCmd.java | 113 + .../project/UpdateProjectInvitationCmd.java | 115 + .../command/user/region/ListRegionsCmd.java | 91 + .../AssignToGlobalLoadBalancerRuleCmd.java | 185 + .../gslb/CreateGlobalLoadBalancerRuleCmd.java | 202 + .../gslb/DeleteGlobalLoadBalancerRuleCmd.java | 125 + .../gslb/ListGlobalLoadBalancerRuleCmd.java | 96 + .../RemoveFromGlobalLoadBalancerRuleCmd.java | 142 + .../gslb/UpdateGlobalLoadBalancerRuleCmd.java | 138 + .../user/resource/GetCloudIdentifierCmd.java | 88 + .../user/resource/ListHypervisorsCmd.java | 86 + .../user/resource/ListResourceLimitsCmd.java | 100 + .../user/resource/UpdateResourceCountCmd.java | 148 + .../user/resource/UpdateResourceLimitCmd.java | 123 + .../AuthorizeSecurityGroupEgressCmd.java | 240 + .../AuthorizeSecurityGroupIngressCmd.java | 239 + .../securitygroup/CreateSecurityGroupCmd.java | 128 + .../securitygroup/DeleteSecurityGroupCmd.java | 137 + .../securitygroup/ListSecurityGroupsCmd.java | 90 + .../RevokeSecurityGroupEgressCmd.java | 116 + .../RevokeSecurityGroupIngressCmd.java | 117 + .../user/snapshot/CreateSnapshotCmd.java | 231 + .../snapshot/CreateSnapshotPolicyCmd.java | 147 + .../user/snapshot/DeleteSnapshotCmd.java | 111 + .../snapshot/DeleteSnapshotPoliciesCmd.java | 91 + .../snapshot/ListSnapshotPoliciesCmd.java | 98 + .../user/snapshot/ListSnapshotsCmd.java | 123 + .../user/snapshot/RevertSnapshotCmd.java | 111 + .../snapshot/UpdateSnapshotPolicyCmd.java | 135 + .../command/user/ssh/CreateSSHKeyPairCmd.java | 104 + .../command/user/ssh/DeleteSSHKeyPairCmd.java | 110 + .../command/user/ssh/ListSSHKeyPairsCmd.java | 87 + .../user/ssh/RegisterSSHKeyPairCmd.java | 113 + .../api/command/user/tag/CreateTagsCmd.java | 136 + .../api/command/user/tag/DeleteTagsCmd.java | 128 + .../api/command/user/tag/ListTagsCmd.java | 85 + .../user/template/CopyTemplateCmd.java | 150 + .../user/template/CreateTemplateCmd.java | 299 + .../user/template/DeleteTemplateCmd.java | 121 + .../user/template/ExtractTemplateCmd.java | 146 + .../GetUploadParamsForTemplateCmd.java | 184 + .../template/ListTemplatePermissionsCmd.java | 57 + .../user/template/ListTemplatesCmd.java | 135 + .../user/template/RegisterTemplateCmd.java | 280 + .../user/template/UpdateTemplateCmd.java | 82 + .../UpdateTemplatePermissionsCmd.java | 53 + .../api/command/user/vm/AddIpToVmNicCmd.java | 196 + .../api/command/user/vm/AddNicToVMCmd.java | 126 + .../api/command/user/vm/DeployVMCmd.java | 628 + .../api/command/user/vm/DestroyVMCmd.java | 137 + .../api/command/user/vm/GetVMPasswordCmd.java | 86 + .../api/command/user/vm/ListNicsCmd.java | 153 + .../api/command/user/vm/ListVMsCmd.java | 246 + .../api/command/user/vm/RebootVMCmd.java | 115 + .../command/user/vm/RemoveIpFromVmNicCmd.java | 182 + .../command/user/vm/RemoveNicFromVMCmd.java | 119 + .../command/user/vm/ResetVMPasswordCmd.java | 131 + .../api/command/user/vm/ResetVMSSHKeyCmd.java | 154 + .../api/command/user/vm/RestoreVMCmd.java | 112 + .../api/command/user/vm/ScaleVMCmd.java | 162 + .../api/command/user/vm/StartVMCmd.java | 170 + .../api/command/user/vm/StopVMCmd.java | 131 + .../user/vm/UpdateDefaultNicForVMCmd.java | 120 + .../api/command/user/vm/UpdateVMCmd.java | 195 + .../api/command/user/vm/UpdateVmNicIpCmd.java | 186 + .../api/command/user/vm/UpgradeVMCmd.java | 139 + .../user/vmgroup/CreateVMGroupCmd.java | 112 + .../user/vmgroup/DeleteVMGroupCmd.java | 86 + .../command/user/vmgroup/ListVMGroupsCmd.java | 74 + .../user/vmgroup/UpdateVMGroupCmd.java | 93 + .../user/vmsnapshot/CreateVMSnapshotCmd.java | 134 + .../user/vmsnapshot/DeleteVMSnapshotCmd.java | 92 + .../user/vmsnapshot/ListVMSnapshotCmd.java | 87 + .../vmsnapshot/RevertToVMSnapshotCmd.java | 99 + .../user/volume/AddResourceDetailCmd.java | 123 + .../command/user/volume/AttachVolumeCmd.java | 129 + .../command/user/volume/CreateVolumeCmd.java | 246 + .../command/user/volume/DeleteVolumeCmd.java | 94 + .../command/user/volume/DetachVolumeCmd.java | 153 + .../command/user/volume/ExtractVolumeCmd.java | 158 + .../volume/GetUploadParamsForVolumeCmd.java | 85 + .../user/volume/ListResourceDetailsCmd.java | 98 + .../command/user/volume/ListVolumesCmd.java | 156 + .../command/user/volume/MigrateVolumeCmd.java | 122 + .../user/volume/RemoveResourceDetailCmd.java | 105 + .../command/user/volume/ResizeVolumeCmd.java | 182 + .../command/user/volume/UpdateVolumeCmd.java | 176 + .../command/user/volume/UploadVolumeCmd.java | 180 + .../user/vpc/CreateStaticRouteCmd.java | 182 + .../api/command/user/vpc/CreateVPCCmd.java | 209 + .../user/vpc/DeleteStaticRouteCmd.java | 127 + .../api/command/user/vpc/DeleteVPCCmd.java | 116 + .../user/vpc/ListPrivateGatewaysCmd.java | 107 + .../command/user/vpc/ListStaticRoutesCmd.java | 112 + .../command/user/vpc/ListVPCOfferingsCmd.java | 117 + .../api/command/user/vpc/ListVPCsCmd.java | 154 + .../api/command/user/vpc/RestartVPCCmd.java | 139 + .../api/command/user/vpc/UpdateVPCCmd.java | 136 + .../api/command/user/vpn/AddVpnUserCmd.java | 153 + .../user/vpn/CreateRemoteAccessVpnCmd.java | 204 + .../user/vpn/CreateVpnConnectionCmd.java | 185 + .../user/vpn/CreateVpnCustomerGatewayCmd.java | 188 + .../command/user/vpn/CreateVpnGatewayCmd.java | 138 + .../user/vpn/DeleteRemoteAccessVpnCmd.java | 111 + .../user/vpn/DeleteVpnConnectionCmd.java | 99 + .../user/vpn/DeleteVpnCustomerGatewayCmd.java | 100 + .../command/user/vpn/DeleteVpnGatewayCmd.java | 94 + .../user/vpn/ListRemoteAccessVpnsCmd.java | 114 + .../user/vpn/ListVpnConnectionsCmd.java | 102 + .../user/vpn/ListVpnCustomerGatewaysCmd.java | 83 + .../command/user/vpn/ListVpnGatewaysCmd.java | 103 + .../api/command/user/vpn/ListVpnUsersCmd.java | 85 + .../command/user/vpn/RemoveVpnUserCmd.java | 124 + .../user/vpn/ResetVpnConnectionCmd.java | 118 + .../user/vpn/UpdateRemoteAccessVpnCmd.java | 112 + .../user/vpn/UpdateVpnConnectionCmd.java | 100 + .../user/vpn/UpdateVpnCustomerGatewayCmd.java | 182 + .../command/user/vpn/UpdateVpnGatewayCmd.java | 101 + .../api/command/user/zone/ListZonesCmd.java | 134 + .../api/response/AccountResponse.java | 488 + .../api/response/AddIpToVmNicResponse.java | 87 + .../api/response/AlertResponse.java | 76 + ...plicationLoadBalancerInstanceResponse.java | 65 + .../ApplicationLoadBalancerResponse.java | 163 + .../ApplicationLoadBalancerRuleResponse.java | 54 + .../api/response/AsyncJobResponse.java | 121 + .../response/AuthenticationCmdResponse.java | 22 + .../api/response/AutoScalePolicyResponse.java | 121 + .../response/AutoScaleVmGroupResponse.java | 166 + .../response/AutoScaleVmProfileResponse.java | 181 + .../api/response/CapabilitiesResponse.java | 138 + .../api/response/CapabilityResponse.java | 62 + .../api/response/CapacityResponse.java | 145 + .../api/response/CloudIdentifierResponse.java | 63 + .../api/response/ClusterResponse.java | 199 + .../api/response/ConditionResponse.java | 126 + .../api/response/ConfigurationResponse.java | 89 + .../response/ControlledEntityResponse.java | 30 + .../ControlledViewEntityResponse.java | 30 + .../api/response/CounterResponse.java | 71 + .../api/response/CreateCmdResponse.java | 36 + .../response/CreateSSHKeyPairResponse.java | 43 + .../response/CustomCertificateResponse.java | 37 + .../response/DeploymentPlannersResponse.java | 37 + .../api/response/DiskOfferingResponse.java | 266 + .../api/response/DomainResponse.java | 423 + .../api/response/DomainRouterResponse.java | 448 + .../api/response/EventResponse.java | 140 + .../api/response/EventTypeResponse.java | 37 + .../api/response/ExceptionResponse.java | 83 + .../response/ExternalFirewallResponse.java | 159 + .../ExternalLoadBalancerResponse.java | 112 + .../api/response/ExtractResponse.java | 217 + .../api/response/FirewallResponse.java | 133 + .../api/response/FirewallRuleResponse.java | 229 + .../api/response/GetUploadParamsResponse.java | 84 + .../api/response/GetVMPasswordResponse.java | 47 + .../response/GlobalLoadBalancerResponse.java | 148 + .../cloudstack/api/response/GpuResponse.java | 45 + .../api/response/GuestOSCategoryResponse.java | 52 + .../api/response/GuestOSResponse.java | 77 + .../api/response/GuestOsMappingResponse.java | 113 + .../api/response/GuestVlanRangeResponse.java | 107 + .../response/HostForMigrationResponse.java | 410 + .../cloudstack/api/response/HostResponse.java | 449 + .../api/response/HostTagResponse.java | 60 + .../HypervisorCapabilitiesResponse.java | 125 + .../api/response/HypervisorResponse.java | 37 + .../api/response/IPAddressResponse.java | 291 + .../response/ImageStoreDetailResponse.java | 88 + .../api/response/ImageStoreResponse.java | 154 + .../api/response/InstanceGroupResponse.java | 101 + .../InternalLoadBalancerElementResponse.java | 53 + .../response/IpForwardingRuleResponse.java | 145 + .../api/response/IsoVmResponse.java | 165 + .../api/response/IsolationMethodResponse.java | 34 + .../response/LBHealthCheckPolicyResponse.java | 111 + .../api/response/LBHealthCheckResponse.java | 101 + .../response/LBStickinessPolicyResponse.java | 139 + .../api/response/LBStickinessResponse.java | 131 + .../cloudstack/api/response/ListResponse.java | 52 + .../api/response/LoadBalancerResponse.java | 194 + .../LoadBalancerRuleVmMapResponse.java | 53 + .../api/response/LoginCmdResponse.java | 157 + .../api/response/LogoutCmdResponse.java | 37 + .../api/response/NetworkACLItemResponse.java | 143 + .../api/response/NetworkACLResponse.java | 69 + .../api/response/NetworkDeviceResponse.java | 37 + .../api/response/NetworkOfferingResponse.java | 209 + .../api/response/NetworkResponse.java | 432 + .../api/response/NicDetailResponse.java | 75 + .../cloudstack/api/response/NicResponse.java | 208 + .../api/response/NicSecondaryIpResponse.java | 96 + .../api/response/PhysicalNetworkResponse.java | 136 + .../cloudstack/api/response/PodResponse.java | 150 + .../api/response/PortableIpRangeResponse.java | 96 + .../api/response/PortableIpResponse.java | 112 + .../api/response/PrivateGatewayResponse.java | 177 + .../api/response/ProjectAccountResponse.java | 108 + .../response/ProjectInvitationResponse.java | 98 + .../api/response/ProjectResponse.java | 417 + .../api/response/ProviderResponse.java | 116 + .../api/response/RegionResponse.java | 80 + .../api/response/RegisterResponse.java | 48 + .../api/response/RemoteAccessVpnResponse.java | 132 + .../api/response/ResourceCountResponse.java | 88 + .../api/response/ResourceDetailResponse.java | 82 + .../ResourceLimitAndCountResponse.java | 95 + .../api/response/ResourceLimitResponse.java | 90 + .../api/response/ResourceTagResponse.java | 142 + .../api/response/SSHKeyPairResponse.java | 91 + .../api/response/SecurityGroupResponse.java | 199 + .../response/SecurityGroupRuleResponse.java | 177 + .../api/response/ServiceOfferingResponse.java | 379 + .../api/response/ServiceResponse.java | 53 + .../Site2SiteCustomerGatewayResponse.java | 179 + .../Site2SiteVpnConnectionResponse.java | 232 + .../response/Site2SiteVpnGatewayResponse.java | 117 + .../api/response/SnapshotPolicyResponse.java | 113 + .../api/response/SnapshotResponse.java | 201 + .../response/SnapshotScheduleResponse.java | 74 + .../api/response/SslCertResponse.java | 116 + .../api/response/StaticRouteResponse.java | 129 + .../api/response/StatusResponse.java | 34 + .../StorageNetworkIpRangeResponse.java | 100 + .../api/response/StoragePoolResponse.java | 311 + .../api/response/StorageProviderResponse.java | 63 + .../api/response/StorageTagResponse.java | 60 + .../api/response/SuccessResponse.java | 55 + .../response/SystemVmInstanceResponse.java | 100 + .../api/response/SystemVmResponse.java | 357 + .../response/TemplatePermissionsResponse.java | 71 + .../api/response/TemplateResponse.java | 363 + .../api/response/TemplateZoneResponse.java | 133 + .../api/response/TrafficMonitorResponse.java | 86 + .../TrafficTypeImplementorResponse.java | 41 + .../api/response/TrafficTypeResponse.java | 106 + .../UpgradeRouterTemplateResponse.java | 41 + .../api/response/UpgradeVmResponse.java | 322 + .../api/response/UsageRecordResponse.java | 255 + .../api/response/UsageTypeResponse.java | 57 + .../cloudstack/api/response/UserResponse.java | 235 + .../api/response/UserVmResponse.java | 792 + .../api/response/VMSnapshotResponse.java | 230 + .../api/response/VMUserDataResponse.java | 44 + .../cloudstack/api/response/VgpuResponse.java | 90 + .../VirtualRouterProviderResponse.java | 98 + .../api/response/VlanIpRangeResponse.java | 218 + .../api/response/VolumeDetailResponse.java | 75 + .../api/response/VolumeResponse.java | 525 + .../api/response/VpcOfferingResponse.java | 104 + .../cloudstack/api/response/VpcResponse.java | 226 + .../api/response/VpnUsersResponse.java | 103 + .../cloudstack/api/response/ZoneResponse.java | 238 + .../config/ApiServiceConfiguration.java | 42 + .../cloudstack/context/CallContext.java | 379 + .../context/CallContextListener.java | 52 + .../apache/cloudstack/context/LogContext.java | 302 + .../context/LogContextListener.java | 50 + .../org/apache/cloudstack/jobs/JobInfo.java | 78 + .../network/ExternalNetworkDeviceManager.java | 75 + .../InternalLoadBalancerElementService.java | 55 + .../lb/ApplicationLoadBalancerContainer.java | 28 + .../lb/ApplicationLoadBalancerRule.java | 24 + .../lb/ApplicationLoadBalancerService.java | 44 + .../lb/InternalLoadBalancerVMService.java | 33 + .../apache/cloudstack/query/QueryService.java | 144 + .../apache/cloudstack/region/PortableIp.java | 57 + .../cloudstack/region/PortableIpRange.java | 38 + .../org/apache/cloudstack/region/Region.java | 81 + .../cloudstack/region/RegionService.java | 152 + .../apache/cloudstack/region/RegionSync.java | 33 + .../org/apache/cloudstack/usage/Usage.java | 66 + .../apache/cloudstack/usage/UsageService.java | 68 + .../apache/cloudstack/usage/UsageTypes.java | 69 + .../cloudstack/api-config/module.properties | 18 + .../api-config/spring-api-config-context.xml | 32 + .../cloudstack/api-planner/module.properties | 18 + .../spring-api-planner-context.xml | 34 + .../java/com/cloud/network/NetworksTest.java | 117 + .../src/test/java/integration/api/__init__.py | 16 + .../src/test/java/integration/api/setup.py | 16 + .../java/integration/api/test/__init__.py | 16 + .../integration/api/test/account/__init__.py | 16 + .../api/test/account/testCreateAccount.py | 60 + .../apache/cloudstack/api/ApiCmdTestUtil.java | 33 + .../apache/cloudstack/api/BaseCmdTest.java | 69 + .../admin/account/CreateAccountCmdTest.java | 101 + .../CreateSecondaryStagingStoreCmdTest.java | 61 + .../command/admin/user/CreateUserCmdTest.java | 97 + .../admin/vpc/CreateVPCOfferingCmdTest.java | 63 + .../command/test/ActivateProjectCmdTest.java | 85 + .../test/AddAccountToProjectCmdTest.java | 174 + .../api/command/test/AddClusterCmdTest.java | 117 + .../api/command/test/AddHostCmdTest.java | 160 + .../api/command/test/AddIpToVmNicTest.java | 123 + .../AddNetworkServiceProviderCmdTest.java | 115 + .../test/AddSecondaryStorageCmdTest.java | 103 + .../api/command/test/AddVpnUserCmdTest.java | 115 + .../api/command/test/ListCfgCmdTest.java | 79 + .../api/command/test/RegionCmdTest.java | 100 + .../api/command/test/ScaleVMCmdTest.java | 117 + .../api/command/test/UpdateCfgCmdTest.java | 106 + .../test/UpdateHostPasswordCmdTest.java | 98 + .../api/command/test/UpdateVmNicIpTest.java | 90 + .../api/command/test/UsageCmdTest.java | 91 + .../api/response/HostResponseTest.java | 81 + .../cloudstack/context/CallContextTest.java | 83 + .../cloudstack/test/utils/SpringUtils.java | 114 + cosmic-core/build/replace.properties | 33 + cosmic-core/developer/developer-prefill.sql | 86 + cosmic-core/developer/pom.xml | 161 + cosmic-core/engine/api/pom.xml | 58 + .../java/com/cloud/vm/VirtualMachineGuru.java | 62 + .../com/cloud/vm/VirtualMachineManager.java | 202 + .../engine/cloud/entity/api/BackupEntity.java | 29 + .../engine/cloud/entity/api/EdgeService.java | 23 + .../cloud/entity/api/NetworkEntity.java | 39 + .../engine/cloud/entity/api/NicEntity.java | 29 + .../cloud/entity/api/SnapshotEntity.java | 49 + .../cloud/entity/api/TemplateEntity.java | 29 + .../entity/api/VirtualMachineEntity.java | 166 + .../engine/cloud/entity/api/VolumeEntity.java | 83 + .../datacenter/entity/api/ClusterEntity.java | 40 + .../entity/api/DataCenterResourceEntity.java | 96 + .../datacenter/entity/api/HostEntity.java | 60 + .../entity/api/OrganizationScope.java | 23 + .../datacenter/entity/api/PodEntity.java | 42 + .../datacenter/entity/api/StorageEntity.java | 25 + .../datacenter/entity/api/ZoneEntity.java | 44 + .../engine/entity/api/CloudStackEntity.java | 95 + .../InsufficientCapacityException.java | 23 + .../service/NetworkOrchestrationService.java | 259 + .../service/VolumeOrchestrationService.java | 127 + .../rest/service/api/ClusterRestService.java | 86 + .../rest/service/api/NetworkRestService.java | 59 + .../rest/service/api/PodRestService.java | 78 + .../api/VirtualMachineRestService.java | 52 + .../rest/service/api/VolumeRestService.java | 73 + .../rest/service/api/ZoneRestService.java | 89 + .../engine/service/api/DirectoryService.java | 37 + .../engine/service/api/EntityService.java | 57 + .../service/api/OperationsServices.java | 56 + .../service/api/OrchestrationService.java | 93 + .../service/api/ProvisioningService.java | 71 + .../api/hypervisor/ComputeSubsystem.java | 30 + .../api/network/NetworkServiceProvider.java | 47 + .../api/network/NetworkSubsystem.java | 35 + .../subsystem/api/storage/AbstractScope.java | 26 + .../subsystem/api/storage/ChapInfo.java | 29 + .../subsystem/api/storage/ClusterScope.java | 54 + .../api/storage/CopyCommandResult.java | 42 + .../api/storage/CreateCmdResult.java | 47 + .../api/storage/DataMotionService.java | 35 + .../api/storage/DataMotionStrategy.java | 39 + .../subsystem/api/storage/DataObject.java | 51 + .../api/storage/DataObjectInStore.java | 33 + .../subsystem/api/storage/DataStore.java | 42 + .../api/storage/DataStoreCapabilities.java | 24 + .../api/storage/DataStoreDriver.java | 45 + .../api/storage/DataStoreLifeCycle.java | 42 + .../api/storage/DataStoreManager.java | 47 + .../api/storage/DataStoreProvider.java | 48 + .../api/storage/DataStoreProviderManager.java | 32 + .../subsystem/api/storage/EndPoint.java | 34 + .../api/storage/EndPointSelector.java | 41 + .../subsystem/api/storage/HostScope.java | 52 + .../api/storage/HypervisorHostListener.java | 27 + .../api/storage/ImageStoreProvider.java | 28 + .../ObjectInDataStoreStateMachine.java | 60 + .../api/storage/PrimaryDataStore.java | 37 + .../api/storage/PrimaryDataStoreDriver.java | 52 + .../api/storage/PrimaryDataStoreInfo.java | 60 + .../storage/PrimaryDataStoreLifeCycle.java | 32 + .../storage/PrimaryDataStoreParameters.java | 280 + .../api/storage/PrimaryDataStoreProvider.java | 20 + .../engine/subsystem/api/storage/Scope.java | 29 + .../api/storage/SnapshotDataFactory.java | 35 + .../subsystem/api/storage/SnapshotInfo.java | 41 + .../api/storage/SnapshotProfile.java | 27 + .../subsystem/api/storage/SnapshotResult.java | 48 + .../api/storage/SnapshotService.java | 32 + .../api/storage/SnapshotStrategy.java | 35 + .../subsystem/api/storage/StorageAction.java | 27 + .../api/storage/StorageCacheManager.java | 42 + .../subsystem/api/storage/StorageEvent.java | 23 + .../api/storage/StoragePoolAllocator.java | 54 + .../api/storage/StorageStrategyFactory.java | 39 + .../api/storage/StrategyPriority.java | 21 + .../api/storage/TemplateDataFactory.java | 39 + .../subsystem/api/storage/TemplateEvent.java | 23 + .../subsystem/api/storage/TemplateInfo.java | 28 + .../api/storage/TemplateService.java | 68 + .../subsystem/api/storage/TemplateState.java | 23 + .../api/storage/VMSnapshotOptions.java | 31 + .../api/storage/VMSnapshotStrategy.java | 31 + .../api/storage/VolumeDataFactory.java | 35 + .../subsystem/api/storage/VolumeInfo.java | 56 + .../subsystem/api/storage/VolumeService.java | 111 + .../subsystem/api/storage/ZoneScope.java | 42 + .../api/storage/disktype/DiskFormat.java | 37 + .../subsystem/api/storage/disktype/QCOW2.java | 16 + .../api/storage/disktype/Unknown.java | 16 + .../subsystem/api/storage/disktype/VHD.java | 16 + .../subsystem/api/storage/disktype/VHDX.java | 16 + .../subsystem/api/storage/disktype/VMDK.java | 16 + .../api/storage/disktype/VolumeDiskType.java | 16 + .../storage/disktype/VolumeDiskTypeBase.java | 16 + .../disktype/VolumeDiskTypeHelper.java | 16 + .../subsystem/api/storage/type/BaseImage.java | 23 + .../subsystem/api/storage/type/DataDisk.java | 23 + .../subsystem/api/storage/type/Iso.java | 23 + .../subsystem/api/storage/type/RootDisk.java | 23 + .../subsystem/api/storage/type/Unknown.java | 24 + .../api/storage/type/VolumeType.java | 22 + .../api/storage/type/VolumeTypeBase.java | 52 + .../api/storage/type/VolumeTypeHelper.java | 48 + .../storage/command/CommandResult.java | 52 + .../image/datastore/ImageStoreEntity.java | 49 + .../image/datastore/ImageStoreInfo.java | 27 + .../core/spring-engine-api-core-context.xml | 44 + .../subsystem/api/storage/ScopeTest.java | 58 + .../storage/type/VolumeTypeHelperTest.java | 101 + cosmic-core/engine/components-api/pom.xml | 43 + .../java/com/cloud/agent/AgentManager.java | 149 + .../main/java/com/cloud/agent/Listener.java | 119 + .../java/com/cloud/alert/AlertManager.java | 44 + .../com/cloud/capacity/CapacityManager.java | 109 + .../configuration/ConfigurationManager.java | 245 + .../deploy/DeploymentPlanningManager.java | 52 + .../java/com/cloud/event/UsageEventUtils.java | 215 + .../com/cloud/ha/HighAvailabilityManager.java | 111 + .../hypervisor/HypervisorGuruManager.java | 27 + .../com/cloud/network/IpAddressManager.java | 180 + .../cloud/network/NetworkStateListener.java | 118 + .../java/com/cloud/network/addr/PublicIp.java | 255 + .../network/lb/LoadBalancingRulesManager.java | 73 + .../cloud/network/rules/FirewallManager.java | 90 + .../com/cloud/network/rules/RulesManager.java | 67 + .../network/rules/StaticNatRuleImpl.java | 156 + .../cloud/network/vpc/NetworkACLManager.java | 155 + .../com/cloud/network/vpc/VpcManager.java | 168 + .../java/com/cloud/resource/Discoverer.java | 54 + .../com/cloud/resource/ResourceManager.java | 194 + .../cloud/resource/ResourceStateAdapter.java | 60 + .../com/cloud/template/TemplateManager.java | 128 + .../com/cloud/vm/ReservationContextImpl.java | 96 + .../cloud/vm/VirtualMachineProfileImpl.java | 309 + .../src/main/java/com/cloud/vm/VmWork.java | 52 + .../java/com/cloud/vm/VmWorkAttachVolume.java | 38 + .../java/com/cloud/vm/VmWorkConstants.java | 24 + .../java/com/cloud/vm/VmWorkDetachVolume.java | 32 + .../com/cloud/vm/VmWorkExtractVolume.java | 38 + .../java/com/cloud/vm/VmWorkJobHandler.java | 25 + .../com/cloud/vm/VmWorkJobHandlerProxy.java | 132 + .../com/cloud/vm/VmWorkMigrateVolume.java | 44 + .../java/com/cloud/vm/VmWorkResizeVolume.java | 71 + .../java/com/cloud/vm/VmWorkSerializer.java | 74 + .../cloud/vm/VmWorkTakeVolumeSnapshot.java | 52 + .../cloud/vm/snapshot/VMSnapshotManager.java | 45 + .../cloudstack/compute/ComputeGuru.java | 36 + ...ing-engine-components-api-core-context.xml | 30 + cosmic-core/engine/network/pom.xml | 31 + .../network/NetworkOrchestrator.java | 40 + cosmic-core/engine/orchestration/pom.xml | 63 + .../com/cloud/agent/manager/AgentAttache.java | 527 + .../cloud/agent/manager/AgentManagerImpl.java | 1660 ++ .../agent/manager/ClusteredAgentAttache.java | 257 + .../manager/ClusteredAgentManagerImpl.java | 1456 ++ .../manager/ClusteredDirectAgentAttache.java | 66 + .../agent/manager/ConnectedAgentAttache.java | 113 + .../agent/manager/DirectAgentAttache.java | 353 + .../com/cloud/agent/manager/DummyAttache.java | 44 + .../com/cloud/agent/manager/Routable.java | 29 + .../agent/manager/SynchronousListener.java | 136 + .../ClusteredAgentRebalanceService.java | 30 + .../agentlb/AgentLoadBalancerPlanner.java | 28 + .../ClusterBasedAgentLoadBalancerPlanner.java | 150 + .../ClusteredVirtualMachineManagerImpl.java | 62 + .../cloud/vm/VirtualMachineManagerImpl.java | 4628 +++++ .../vm/VirtualMachinePowerStateSync.java | 33 + .../vm/VirtualMachinePowerStateSyncImpl.java | 183 + .../com/cloud/vm/VmWorkAddVmToNetwork.java | 40 + .../com/cloud/vm/VmWorkJobDispatcher.java | 121 + .../cloud/vm/VmWorkJobWakeupDispatcher.java | 147 + .../main/java/com/cloud/vm/VmWorkMigrate.java | 87 + .../java/com/cloud/vm/VmWorkMigrateAway.java | 36 + .../com/cloud/vm/VmWorkMigrateForScale.java | 36 + .../cloud/vm/VmWorkMigrateWithStorage.java | 49 + .../main/java/com/cloud/vm/VmWorkReboot.java | 60 + .../java/com/cloud/vm/VmWorkReconfigure.java | 42 + .../com/cloud/vm/VmWorkRemoveNicFromVm.java | 33 + .../cloud/vm/VmWorkRemoveVmFromNetwork.java | 43 + .../main/java/com/cloud/vm/VmWorkStart.java | 134 + .../main/java/com/cloud/vm/VmWorkStop.java | 32 + .../com/cloud/vm/VmWorkStorageMigration.java | 33 + .../cloud/entity/api/VMEntityManager.java | 48 + .../cloud/entity/api/VMEntityManagerImpl.java | 291 + .../entity/api/VirtualMachineEntityImpl.java | 267 + .../entity/api/ClusterEntityImpl.java | 205 + .../entity/api/DataCenterResourceManager.java | 47 + .../api/DataCenterResourceManagerImpl.java | 127 + .../datacenter/entity/api/HostEntityImpl.java | 213 + .../datacenter/entity/api/PodEntityImpl.java | 209 + .../datacenter/entity/api/ZoneEntityImpl.java | 198 + .../entity/api/db/ClusterDetailsVO.java | 72 + .../datacenter/entity/api/db/DcDetailVO.java | 71 + .../entity/api/db/EngineCluster.java | 23 + .../entity/api/db/EngineClusterVO.java | 246 + .../entity/api/db/EngineDataCenter.java | 23 + .../entity/api/db/EngineDataCenterVO.java | 504 + .../datacenter/entity/api/db/EngineHost.java | 24 + .../entity/api/db/EngineHostPodVO.java | 243 + .../entity/api/db/EngineHostVO.java | 761 + .../datacenter/entity/api/db/EnginePod.java | 23 + .../entity/api/db/dao/DcDetailsDao.java | 33 + .../entity/api/db/dao/DcDetailsDaoImpl.java | 94 + .../entity/api/db/dao/EngineClusterDao.java | 48 + .../api/db/dao/EngineClusterDaoImpl.java | 309 + .../api/db/dao/EngineDataCenterDao.java | 61 + .../api/db/dao/EngineDataCenterDaoImpl.java | 338 + .../entity/api/db/dao/EngineHostDao.java | 42 + .../entity/api/db/dao/EngineHostDaoImpl.java | 514 + .../entity/api/db/dao/EngineHostPodDao.java | 36 + .../api/db/dao/EngineHostPodDaoImpl.java | 194 + .../entity/api/db/dao/HostDetailsDao.java | 32 + .../entity/api/db/dao/HostDetailsDaoImpl.java | 117 + .../entity/api/db/dao/HostTagsDao.java | 30 + .../entity/api/db/dao/HostTagsDaoImpl.java | 72 + .../orchestration/CloudOrchestrator.java | 319 + .../orchestration/NetworkOrchestrator.java | 3216 ++++ .../orchestration/VolumeOrchestrator.java | 1501 ++ .../service/api/ProvisioningServiceImpl.java | 172 + ...ring-engine-orchestration-core-context.xml | 94 + .../manager/ConnectedAgentAttacheTest.java | 82 + .../agent/manager/DirectAgentAttacheTest.java | 54 + .../vm/VirtualMachineManagerImplTest.java | 507 + .../NetworkOrchestratorTest.java | 163 + .../test/ChildTestConfiguration.java | 47 + .../provisioning/test/ProvisioningTest.java | 136 + .../src/test/resource/provisioningContext.xml | 47 + cosmic-core/engine/pom.xml | 28 + cosmic-core/engine/schema/pom.xml | 33 + .../main/java/com/cloud/alert/AlertVO.java | 194 + .../java/com/cloud/alert/dao/AlertDao.java | 36 + .../com/cloud/alert/dao/AlertDaoImpl.java | 170 + .../java/com/cloud/capacity/CapacityVO.java | 215 + .../com/cloud/capacity/dao/CapacityDao.java | 61 + .../cloud/capacity/dao/CapacityDaoImpl.java | 1021 ++ .../com/cloud/certificate/CertificateVO.java | 66 + .../cloud/certificate/dao/CertificateDao.java | 24 + .../certificate/dao/CertificateDaoImpl.java | 49 + .../cluster/agentlb/HostTransferMapVO.java | 100 + .../agentlb/dao/HostTransferMapDao.java | 45 + .../agentlb/dao/HostTransferMapDaoImpl.java | 143 + .../cloud/configuration/ResourceCountVO.java | 139 + .../cloud/configuration/ResourceLimitVO.java | 126 + .../configuration/dao/ResourceCountDao.java | 60 + .../dao/ResourceCountDaoImpl.java | 237 + .../configuration/dao/ResourceLimitDao.java | 37 + .../dao/ResourceLimitDaoImpl.java | 111 + .../java/com/cloud/dc/AccountVlanMapVO.java | 67 + .../java/com/cloud/dc/ClusterDetailsDao.java | 35 + .../com/cloud/dc/ClusterDetailsDaoImpl.java | 149 + .../java/com/cloud/dc/ClusterDetailsVO.java | 75 + .../src/main/java/com/cloud/dc/ClusterVO.java | 195 + .../java/com/cloud/dc/ClusterVSMMapVO.java | 61 + .../java/com/cloud/dc/DataCenterDetailVO.java | 82 + .../com/cloud/dc/DataCenterIpAddressVO.java | 116 + .../dc/DataCenterLinkLocalIpAddressVO.java | 109 + .../main/java/com/cloud/dc/DataCenterVO.java | 451 + .../java/com/cloud/dc/DataCenterVnetVO.java | 121 + .../java/com/cloud/dc/DomainVlanMapVO.java | 63 + .../src/main/java/com/cloud/dc/HostPodVO.java | 196 + .../main/java/com/cloud/dc/PodCluster.java | 66 + .../main/java/com/cloud/dc/PodVlanMapVO.java | 63 + .../src/main/java/com/cloud/dc/PodVlanVO.java | 99 + .../cloud/dc/StorageNetworkIpAddressVO.java | 121 + .../com/cloud/dc/StorageNetworkIpRangeVO.java | 187 + .../src/main/java/com/cloud/dc/VlanVO.java | 239 + .../com/cloud/dc/dao/AccountVlanMapDao.java | 32 + .../cloud/dc/dao/AccountVlanMapDaoImpl.java | 72 + .../java/com/cloud/dc/dao/ClusterDao.java | 48 + .../java/com/cloud/dc/dao/ClusterDaoImpl.java | 263 + .../com/cloud/dc/dao/ClusterVSMMapDao.java | 32 + .../cloud/dc/dao/ClusterVSMMapDaoImpl.java | 94 + .../java/com/cloud/dc/dao/DataCenterDao.java | 99 + .../com/cloud/dc/dao/DataCenterDaoImpl.java | 442 + .../cloud/dc/dao/DataCenterDetailsDao.java | 25 + .../dc/dao/DataCenterDetailsDaoImpl.java | 45 + .../cloud/dc/dao/DataCenterIpAddressDao.java | 50 + .../dc/dao/DataCenterIpAddressDaoImpl.java | 273 + .../dao/DataCenterLinkLocalIpAddressDao.java | 38 + .../DataCenterLinkLocalIpAddressDaoImpl.java | 194 + .../com/cloud/dc/dao/DataCenterVnetDao.java | 57 + .../cloud/dc/dao/DataCenterVnetDaoImpl.java | 353 + .../com/cloud/dc/dao/DomainVlanMapDao.java | 28 + .../cloud/dc/dao/DomainVlanMapDaoImpl.java | 74 + .../java/com/cloud/dc/dao/HostPodDao.java | 35 + .../java/com/cloud/dc/dao/HostPodDaoImpl.java | 138 + .../java/com/cloud/dc/dao/PodVlanDao.java | 34 + .../java/com/cloud/dc/dao/PodVlanDaoImpl.java | 143 + .../java/com/cloud/dc/dao/PodVlanMapDao.java | 32 + .../com/cloud/dc/dao/PodVlanMapDaoImpl.java | 72 + .../dc/dao/StorageNetworkIpAddressDao.java | 32 + .../dao/StorageNetworkIpAddressDaoImpl.java | 105 + .../dc/dao/StorageNetworkIpRangeDao.java | 32 + .../dc/dao/StorageNetworkIpRangeDaoImpl.java | 70 + .../main/java/com/cloud/dc/dao/VlanDao.java | 59 + .../java/com/cloud/dc/dao/VlanDaoImpl.java | 368 + .../main/java/com/cloud/domain/DomainVO.java | 214 + .../java/com/cloud/domain/dao/DomainDao.java | 43 + .../com/cloud/domain/dao/DomainDaoImpl.java | 294 + .../main/java/com/cloud/event/EventVO.java | 227 + .../com/cloud/event/UsageEventDetailsVO.java | 70 + .../java/com/cloud/event/UsageEventVO.java | 251 + .../java/com/cloud/event/dao/EventDao.java | 38 + .../com/cloud/event/dao/EventDaoImpl.java | 117 + .../com/cloud/event/dao/UsageEventDao.java | 38 + .../cloud/event/dao/UsageEventDaoImpl.java | 232 + .../cloud/event/dao/UsageEventDetailsDao.java | 32 + .../event/dao/UsageEventDetailsDaoImpl.java | 87 + .../java/com/cloud/gpu/HostGpuGroupsVO.java | 70 + .../main/java/com/cloud/gpu/VGPUTypesVO.java | 155 + .../com/cloud/gpu/dao/HostGpuGroupsDao.java | 60 + .../cloud/gpu/dao/HostGpuGroupsDaoImpl.java | 92 + .../java/com/cloud/gpu/dao/VGPUTypesDao.java | 58 + .../com/cloud/gpu/dao/VGPUTypesDaoImpl.java | 148 + .../main/java/com/cloud/host/DetailVO.java | 74 + .../main/java/com/cloud/host/HostTagVO.java | 66 + .../src/main/java/com/cloud/host/HostVO.java | 733 + .../main/java/com/cloud/host/dao/HostDao.java | 95 + .../java/com/cloud/host/dao/HostDaoImpl.java | 1081 ++ .../com/cloud/host/dao/HostDetailsDao.java | 32 + .../cloud/host/dao/HostDetailsDaoImpl.java | 117 + .../java/com/cloud/host/dao/HostTagsDao.java | 34 + .../com/cloud/host/dao/HostTagsDaoImpl.java | 99 + .../hypervisor/HypervisorCapabilitiesVO.java | 209 + .../dao/HypervisorCapabilitiesDao.java | 38 + .../dao/HypervisorCapabilitiesDaoImpl.java | 104 + .../cloud/network/LBHealthCheckPolicyVO.java | 172 + .../com/cloud/network/UserIpv6AddressVO.java | 192 + .../java/com/cloud/network/VpnUserVO.java | 133 + .../as/AutoScalePolicyConditionMapVO.java | 63 + .../cloud/network/as/AutoScalePolicyVO.java | 159 + .../as/AutoScaleVmGroupPolicyMapVO.java | 66 + .../cloud/network/as/AutoScaleVmGroupVO.java | 232 + .../network/as/AutoScaleVmGroupVmMapVO.java | 62 + .../network/as/AutoScaleVmProfileVO.java | 241 + .../com/cloud/network/as/ConditionVO.java | 136 + .../java/com/cloud/network/as/CounterVO.java | 112 + .../dao/AutoScalePolicyConditionMapDao.java | 32 + .../AutoScalePolicyConditionMapDaoImpl.java | 66 + .../network/as/dao/AutoScalePolicyDao.java | 24 + .../as/dao/AutoScalePolicyDaoImpl.java | 36 + .../network/as/dao/AutoScaleVmGroupDao.java | 30 + .../as/dao/AutoScaleVmGroupDaoImpl.java | 62 + .../as/dao/AutoScaleVmGroupPolicyMapDao.java | 34 + .../dao/AutoScaleVmGroupPolicyMapDaoImpl.java | 74 + .../as/dao/AutoScaleVmGroupVmMapDao.java | 31 + .../as/dao/AutoScaleVmGroupVmMapDaoImpl.java | 53 + .../network/as/dao/AutoScaleVmProfileDao.java | 25 + .../as/dao/AutoScaleVmProfileDaoImpl.java | 35 + .../cloud/network/as/dao/ConditionDao.java | 28 + .../network/as/dao/ConditionDaoImpl.java | 54 + .../com/cloud/network/as/dao/CounterDao.java | 29 + .../cloud/network/as/dao/CounterDaoImpl.java | 67 + .../network/dao/AccountGuestVlanMapDao.java | 33 + .../dao/AccountGuestVlanMapDaoImpl.java | 79 + .../network/dao/AccountGuestVlanMapVO.java | 99 + .../dao/ExternalFirewallDeviceDao.java | 58 + .../dao/ExternalFirewallDeviceDaoImpl.java | 96 + .../network/dao/ExternalFirewallDeviceVO.java | 161 + .../dao/ExternalLoadBalancerDeviceDao.java | 74 + .../ExternalLoadBalancerDeviceDaoImpl.java | 130 + .../dao/ExternalLoadBalancerDeviceVO.java | 243 + .../network/dao/FirewallRulesCidrsDao.java | 32 + .../dao/FirewallRulesCidrsDaoImpl.java | 80 + .../network/dao/FirewallRulesCidrsVO.java | 71 + .../cloud/network/dao/FirewallRulesDao.java | 67 + .../network/dao/FirewallRulesDaoImpl.java | 363 + .../com/cloud/network/dao/IPAddressDao.java | 92 + .../cloud/network/dao/IPAddressDaoImpl.java | 474 + .../com/cloud/network/dao/IPAddressVO.java | 370 + .../dao/InlineLoadBalancerNicMapDao.java | 25 + .../dao/InlineLoadBalancerNicMapDaoImpl.java | 43 + .../dao/InlineLoadBalancerNicMapVO.java | 62 + .../network/dao/LBHealthCheckPolicyDao.java | 32 + .../dao/LBHealthCheckPolicyDaoImpl.java | 67 + .../network/dao/LBStickinessPolicyDao.java | 31 + .../dao/LBStickinessPolicyDaoImpl.java | 64 + .../network/dao/LBStickinessPolicyVO.java | 165 + .../network/dao/LoadBalancerCertMapDao.java | 29 + .../dao/LoadBalancerCertMapDaoImpl.java | 79 + .../network/dao/LoadBalancerCertMapVO.java | 94 + .../cloud/network/dao/LoadBalancerDao.java | 32 + .../network/dao/LoadBalancerDaoImpl.java | 78 + .../network/dao/LoadBalancerVMMapDao.java | 45 + .../network/dao/LoadBalancerVMMapDaoImpl.java | 137 + .../network/dao/LoadBalancerVMMapVO.java | 109 + .../com/cloud/network/dao/LoadBalancerVO.java | 130 + .../network/dao/MonitoringServiceDao.java | 32 + .../network/dao/MonitoringServiceDaoImpl.java | 63 + .../network/dao/MonitoringServiceVO.java | 124 + .../cloud/network/dao/NetworkAccountDao.java | 23 + .../network/dao/NetworkAccountDaoImpl.java | 44 + .../cloud/network/dao/NetworkAccountVO.java | 73 + .../com/cloud/network/dao/NetworkDao.java | 122 + .../com/cloud/network/dao/NetworkDaoImpl.java | 679 + .../cloud/network/dao/NetworkDetailVO.java | 82 + .../cloud/network/dao/NetworkDetailsDao.java | 25 + .../network/dao/NetworkDetailsDaoImpl.java | 30 + .../cloud/network/dao/NetworkDomainDao.java | 29 + .../network/dao/NetworkDomainDaoImpl.java | 73 + .../cloud/network/dao/NetworkDomainVO.java | 72 + .../dao/NetworkExternalFirewallDao.java | 38 + .../dao/NetworkExternalFirewallDaoImpl.java | 59 + .../dao/NetworkExternalFirewallVO.java | 90 + .../dao/NetworkExternalLoadBalancerDao.java | 38 + .../NetworkExternalLoadBalancerDaoImpl.java | 60 + .../dao/NetworkExternalLoadBalancerVO.java | 91 + .../com/cloud/network/dao/NetworkOpDao.java | 29 + .../cloud/network/dao/NetworkOpDaoImpl.java | 85 + .../com/cloud/network/dao/NetworkOpVO.java | 77 + .../network/dao/NetworkRuleConfigDao.java | 27 + .../network/dao/NetworkRuleConfigDaoImpl.java | 50 + .../network/dao/NetworkRuleConfigVO.java | 78 + .../network/dao/NetworkServiceMapDao.java | 46 + .../network/dao/NetworkServiceMapDaoImpl.java | 174 + .../network/dao/NetworkServiceMapVO.java | 89 + .../java/com/cloud/network/dao/NetworkVO.java | 627 + .../dao/OpRouterMonitorServiceDao.java | 25 + .../dao/OpRouterMonitorServiceDaoImpl.java | 27 + .../network/dao/OpRouterMonitorServiceVO.java | 69 + .../cloud/network/dao/PhysicalNetworkDao.java | 30 + .../network/dao/PhysicalNetworkDaoImpl.java | 79 + ...PhysicalNetworkIsolationMethodDaoImpl.java | 71 + .../dao/PhysicalNetworkIsolationMethodVO.java | 69 + .../PhysicalNetworkServiceProviderDao.java | 31 + ...PhysicalNetworkServiceProviderDaoImpl.java | 129 + .../dao/PhysicalNetworkServiceProviderVO.java | 322 + .../dao/PhysicalNetworkTagDaoImpl.java | 63 + .../network/dao/PhysicalNetworkTagVO.java | 69 + .../dao/PhysicalNetworkTrafficTypeDao.java | 36 + .../PhysicalNetworkTrafficTypeDaoImpl.java | 121 + .../dao/PhysicalNetworkTrafficTypeVO.java | 135 + .../cloud/network/dao/PhysicalNetworkVO.java | 251 + .../cloud/network/dao/RemoteAccessVpnDao.java | 36 + .../network/dao/RemoteAccessVpnDaoImpl.java | 90 + .../cloud/network/dao/RemoteAccessVpnVO.java | 174 + .../cloud/network/dao/RouterNetworkDao.java | 27 + .../network/dao/RouterNetworkDaoImpl.java | 63 + .../cloud/network/dao/RouterNetworkVO.java | 75 + .../dao/Site2SiteCustomerGatewayDao.java | 29 + .../dao/Site2SiteCustomerGatewayDaoImpl.java | 65 + .../dao/Site2SiteCustomerGatewayVO.java | 228 + .../dao/Site2SiteVpnConnectionDao.java | 33 + .../dao/Site2SiteVpnConnectionDaoImpl.java | 98 + .../network/dao/Site2SiteVpnConnectionVO.java | 180 + .../network/dao/Site2SiteVpnGatewayDao.java | 23 + .../dao/Site2SiteVpnGatewayDaoImpl.java | 50 + .../network/dao/Site2SiteVpnGatewayVO.java | 137 + .../com/cloud/network/dao/SslCertDao.java | 25 + .../com/cloud/network/dao/SslCertDaoImpl.java | 42 + .../java/com/cloud/network/dao/SslCertVO.java | 129 + .../cloud/network/dao/UserIpv6AddressDao.java | 40 + .../network/dao/UserIpv6AddressDaoImpl.java | 114 + .../network/dao/VirtualRouterProviderDao.java | 33 + .../dao/VirtualRouterProviderDaoImpl.java | 77 + .../com/cloud/network/dao/VpnUserDao.java | 30 + .../com/cloud/network/dao/VpnUserDaoImpl.java | 83 + .../element/VirtualRouterProviderVO.java | 120 + .../cloud/network/rules/FirewallRuleVO.java | 289 + .../network/rules/PortForwardingRuleVO.java | 109 + .../rules/dao/PortForwardingRulesDao.java | 50 + .../rules/dao/PortForwardingRulesDaoImpl.java | 173 + .../network/security/SecurityGroupRuleVO.java | 148 + .../security/SecurityGroupRulesVO.java | 168 + .../security/SecurityGroupVMMapVO.java | 91 + .../network/security/SecurityGroupVO.java | 101 + .../network/security/SecurityGroupWork.java | 39 + .../network/security/SecurityGroupWorkVO.java | 132 + .../network/security/VmRulesetLogVO.java | 83 + .../security/dao/SecurityGroupDao.java | 34 + .../security/dao/SecurityGroupDaoImpl.java | 132 + .../security/dao/SecurityGroupRuleDao.java | 42 + .../dao/SecurityGroupRuleDaoImpl.java | 195 + .../security/dao/SecurityGroupRulesDao.java | 48 + .../dao/SecurityGroupRulesDaoImpl.java | 84 + .../security/dao/SecurityGroupVMMapDao.java | 47 + .../dao/SecurityGroupVMMapDaoImpl.java | 158 + .../security/dao/SecurityGroupWorkDao.java | 46 + .../dao/SecurityGroupWorkDaoImpl.java | 236 + .../network/security/dao/VmRulesetLogDao.java | 29 + .../security/dao/VmRulesetLogDaoImpl.java | 195 + .../network/vpc/NetworkACLItemCidrsDao.java | 41 + .../network/vpc/NetworkACLItemCidrsVO.java | 78 + .../cloud/network/vpc/NetworkACLItemDao.java | 39 + .../cloud/network/vpc/NetworkACLItemVO.java | 255 + .../com/cloud/network/vpc/NetworkACLVO.java | 100 + .../com/cloud/network/vpc/PrivateIpVO.java | 104 + .../com/cloud/network/vpc/StaticRouteVO.java | 141 + .../com/cloud/network/vpc/VpcGatewayVO.java | 223 + .../network/vpc/VpcOfferingServiceMapVO.java | 91 + .../com/cloud/network/vpc/VpcOfferingVO.java | 186 + .../cloud/network/vpc/VpcServiceMapVO.java | 86 + .../java/com/cloud/network/vpc/VpcVO.java | 240 + .../cloud/network/vpc/dao/NetworkACLDao.java | 23 + .../network/vpc/dao/NetworkACLDaoImpl.java | 32 + .../vpc/dao/NetworkACLItemCidrsDaoImpl.java | 103 + .../vpc/dao/NetworkACLItemDaoImpl.java | 176 + .../cloud/network/vpc/dao/PrivateIpDao.java | 74 + .../network/vpc/dao/PrivateIpDaoImpl.java | 160 + .../cloud/network/vpc/dao/StaticRouteDao.java | 32 + .../network/vpc/dao/StaticRouteDaoImpl.java | 107 + .../com/cloud/network/vpc/dao/VpcDao.java | 45 + .../com/cloud/network/vpc/dao/VpcDaoImpl.java | 151 + .../cloud/network/vpc/dao/VpcGatewayDao.java | 33 + .../network/vpc/dao/VpcGatewayDaoImpl.java | 84 + .../cloud/network/vpc/dao/VpcOfferingDao.java | 31 + .../network/vpc/dao/VpcOfferingDaoImpl.java | 67 + .../vpc/dao/VpcOfferingServiceMapDao.java | 40 + .../vpc/dao/VpcOfferingServiceMapDaoImpl.java | 113 + .../network/vpc/dao/VpcServiceMapDao.java | 46 + .../network/vpc/dao/VpcServiceMapDaoImpl.java | 113 + .../offerings/NetworkOfferingDetailsVO.java | 92 + .../NetworkOfferingServiceMapVO.java | 92 + .../cloud/offerings/NetworkOfferingVO.java | 498 + .../offerings/dao/NetworkOfferingDao.java | 64 + .../offerings/dao/NetworkOfferingDaoImpl.java | 192 + .../dao/NetworkOfferingDetailsDao.java | 31 + .../dao/NetworkOfferingDetailsDaoImpl.java | 78 + .../dao/NetworkOfferingServiceMapDao.java | 45 + .../dao/NetworkOfferingServiceMapDaoImpl.java | 169 + .../com/cloud/projects/ProjectAccountVO.java | 97 + .../cloud/projects/ProjectInvitationVO.java | 153 + .../java/com/cloud/projects/ProjectVO.java | 169 + .../cloud/projects/dao/ProjectAccountDao.java | 43 + .../projects/dao/ProjectAccountDaoImpl.java | 155 + .../com/cloud/projects/dao/ProjectDao.java | 37 + .../cloud/projects/dao/ProjectDaoImpl.java | 122 + .../projects/dao/ProjectInvitationDao.java | 44 + .../dao/ProjectInvitationDaoImpl.java | 169 + .../cloud/secstorage/CommandExecLogDao.java | 25 + .../secstorage/CommandExecLogDaoImpl.java | 45 + .../cloud/secstorage/CommandExecLogVO.java | 114 + .../service/ServiceOfferingDetailsVO.java | 86 + .../com/cloud/service/ServiceOfferingVO.java | 334 + .../cloud/service/dao/ServiceOfferingDao.java | 60 + .../service/dao/ServiceOfferingDaoImpl.java | 293 + .../dao/ServiceOfferingDetailsDao.java | 25 + .../dao/ServiceOfferingDetailsDaoImpl.java | 32 + .../com/cloud/storage/DiskOfferingVO.java | 517 + .../com/cloud/storage/GuestOSCategoryVO.java | 65 + .../cloud/storage/GuestOSHypervisorVO.java | 135 + .../java/com/cloud/storage/GuestOSVO.java | 123 + .../com/cloud/storage/LaunchPermissionVO.java | 59 + .../com/cloud/storage/SnapshotPolicyVO.java | 155 + .../com/cloud/storage/SnapshotScheduleVO.java | 135 + .../java/com/cloud/storage/SnapshotVO.java | 258 + .../cloud/storage/StoragePoolHostAssoc.java | 35 + .../com/cloud/storage/StoragePoolHostVO.java | 103 + .../com/cloud/storage/StoragePoolWorkVO.java | 113 + .../main/java/com/cloud/storage/UploadVO.java | 263 + .../com/cloud/storage/VMTemplateDetailVO.java | 82 + .../com/cloud/storage/VMTemplateHostVO.java | 334 + .../storage/VMTemplateStoragePoolVO.java | 303 + .../java/com/cloud/storage/VMTemplateVO.java | 612 + .../com/cloud/storage/VMTemplateZoneVO.java | 118 + .../com/cloud/storage/VolumeDetailVO.java | 83 + .../java/com/cloud/storage/VolumeHostVO.java | 346 + .../main/java/com/cloud/storage/VolumeVO.java | 637 + .../cloud/storage/dao/DiskOfferingDao.java | 35 + .../storage/dao/DiskOfferingDaoImpl.java | 148 + .../cloud/storage/dao/GuestOSCategoryDao.java | 24 + .../storage/dao/GuestOSCategoryDaoImpl.java | 31 + .../com/cloud/storage/dao/GuestOSDao.java | 26 + .../com/cloud/storage/dao/GuestOSDaoImpl.java | 44 + .../storage/dao/GuestOSHypervisorDao.java | 32 + .../storage/dao/GuestOSHypervisorDaoImpl.java | 99 + .../storage/dao/LaunchPermissionDao.java | 73 + .../storage/dao/LaunchPermissionDaoImpl.java | 159 + .../com/cloud/storage/dao/SnapshotDao.java | 62 + .../cloud/storage/dao/SnapshotDaoImpl.java | 340 + .../cloud/storage/dao/SnapshotDetailsDao.java | 26 + .../storage/dao/SnapshotDetailsDaoImpl.java | 28 + .../cloud/storage/dao/SnapshotDetailsVO.java | 84 + .../cloud/storage/dao/SnapshotPolicyDao.java | 46 + .../storage/dao/SnapshotPolicyDaoImpl.java | 116 + .../storage/dao/SnapshotScheduleDao.java | 40 + .../storage/dao/SnapshotScheduleDaoImpl.java | 131 + .../dao/StoragePoolDetailsDaoImpl.java | 47 + .../cloud/storage/dao/StoragePoolHostDao.java | 42 + .../storage/dao/StoragePoolHostDaoImpl.java | 170 + .../cloud/storage/dao/StoragePoolWorkDao.java | 39 + .../storage/dao/StoragePoolWorkDaoImpl.java | 135 + .../java/com/cloud/storage/dao/UploadDao.java | 35 + .../com/cloud/storage/dao/UploadDaoImpl.java | 88 + .../com/cloud/storage/dao/VMTemplateDao.java | 84 + .../cloud/storage/dao/VMTemplateDaoImpl.java | 1090 ++ .../storage/dao/VMTemplateDetailsDao.java | 26 + .../storage/dao/VMTemplateDetailsDaoImpl.java | 31 + .../cloud/storage/dao/VMTemplateHostDao.java | 68 + .../storage/dao/VMTemplateHostDaoImpl.java | 431 + .../cloud/storage/dao/VMTemplatePoolDao.java | 49 + .../storage/dao/VMTemplatePoolDaoImpl.java | 298 + .../cloud/storage/dao/VMTemplateZoneDao.java | 35 + .../storage/dao/VMTemplateZoneDaoImpl.java | 97 + .../java/com/cloud/storage/dao/VolumeDao.java | 125 + .../com/cloud/storage/dao/VolumeDaoImpl.java | 690 + .../cloud/storage/dao/VolumeDetailsDao.java | 26 + .../storage/dao/VolumeDetailsDaoImpl.java | 32 + .../com/cloud/storage/dao/VolumeHostDao.java | 41 + .../cloud/storage/dao/VolumeHostDaoImpl.java | 180 + .../java/com/cloud/tags/ResourceTagVO.java | 174 + .../com/cloud/tags/dao/ResourceTagDao.java | 38 + .../cloud/tags/dao/ResourceTagsDaoImpl.java | 69 + .../com/cloud/upgrade/DatabaseCreator.java | 234 + .../upgrade/DatabaseIntegrityChecker.java | 306 + .../cloud/upgrade/DatabaseUpgradeChecker.java | 315 + .../upgrade/dao/BareMetalRemovalUpdater.java | 124 + .../upgrade/dao/DatabaseAccessObject.java | 87 + .../java/com/cloud/upgrade/dao/DbUpgrade.java | 45 + .../com/cloud/upgrade/dao/DbUpgradeUtils.java | 44 + .../cloud/upgrade/dao/LegacyDbUpgrade.java | 43 + .../com/cloud/upgrade/dao/Upgrade40to41.java | 188 + .../cloud/upgrade/dao/Upgrade410to420.java | 2509 +++ .../cloud/upgrade/dao/Upgrade420to421.java | 251 + .../cloud/upgrade/dao/Upgrade421to430.java | 206 + .../cloud/upgrade/dao/Upgrade430to440.java | 240 + .../cloud/upgrade/dao/Upgrade431to440.java | 40 + .../cloud/upgrade/dao/Upgrade432to440.java | 40 + .../cloud/upgrade/dao/Upgrade440to441.java | 70 + .../cloud/upgrade/dao/Upgrade441to442.java | 65 + .../cloud/upgrade/dao/Upgrade442to450.java | 176 + .../cloud/upgrade/dao/Upgrade443to444.java | 65 + .../cloud/upgrade/dao/Upgrade443to450.java | 31 + .../cloud/upgrade/dao/Upgrade444to450.java | 31 + .../cloud/upgrade/dao/Upgrade450to451.java | 174 + .../cloud/upgrade/dao/Upgrade451to452.java | 69 + .../cloud/upgrade/dao/Upgrade452to453.java | 69 + .../cloud/upgrade/dao/Upgrade452to460.java | 322 + .../cloud/upgrade/dao/Upgrade453to460.java | 30 + .../cloud/upgrade/dao/Upgrade460to461.java | 69 + .../cloud/upgrade/dao/Upgrade461to470.java | 86 + .../cloud/upgrade/dao/Upgrade470to471.java | 69 + .../cloud/upgrade/dao/Upgrade471to480.java | 69 + .../cloud/upgrade/dao/Upgrade480to500.java | 52 + .../cloud/upgrade/dao/Upgrade500to501.java | 58 + .../cloud/upgrade/dao/Upgrade501to510.java | 60 + .../upgrade/dao/UpgradeSnapshot217to224.java | 60 + .../upgrade/dao/UpgradeSnapshot223to224.java | 60 + .../com/cloud/upgrade/dao/VersionDao.java | 26 + .../com/cloud/upgrade/dao/VersionDaoImpl.java | 155 + .../java/com/cloud/upgrade/dao/VersionVO.java | 91 + .../usage/ExternalPublicIpStatisticsVO.java | 98 + .../com/cloud/usage/UsageIPAddressVO.java | 123 + .../main/java/com/cloud/usage/UsageJobVO.java | 182 + .../usage/UsageLoadBalancerPolicyVO.java | 93 + .../cloud/usage/UsageNetworkOfferingVO.java | 119 + .../java/com/cloud/usage/UsageNetworkVO.java | 146 + .../usage/UsagePortForwardingRuleVO.java | 93 + .../com/cloud/usage/UsageSecurityGroupVO.java | 98 + .../java/com/cloud/usage/UsageStorageVO.java | 137 + .../com/cloud/usage/UsageVMInstanceVO.java | 181 + .../com/cloud/usage/UsageVMSnapshotVO.java | 123 + .../main/java/com/cloud/usage/UsageVO.java | 386 + .../java/com/cloud/usage/UsageVPNUserVO.java | 106 + .../java/com/cloud/usage/UsageVmDiskVO.java | 182 + .../java/com/cloud/usage/UsageVolumeVO.java | 117 + .../dao/ExternalPublicIpStatisticsDao.java | 32 + .../ExternalPublicIpStatisticsDaoImpl.java | 75 + .../java/com/cloud/usage/dao/UsageDao.java | 62 + .../com/cloud/usage/dao/UsageDaoImpl.java | 507 + .../cloud/usage/dao/UsageIPAddressDao.java | 29 + .../usage/dao/UsageIPAddressDaoImpl.java | 150 + .../java/com/cloud/usage/dao/UsageJobDao.java | 40 + .../com/cloud/usage/dao/UsageJobDaoImpl.java | 199 + .../usage/dao/UsageLoadBalancerPolicyDao.java | 31 + .../dao/UsageLoadBalancerPolicyDaoImpl.java | 169 + .../com/cloud/usage/dao/UsageNetworkDao.java | 31 + .../cloud/usage/dao/UsageNetworkDaoImpl.java | 133 + .../usage/dao/UsageNetworkOfferingDao.java | 29 + .../dao/UsageNetworkOfferingDaoImpl.java | 156 + .../usage/dao/UsagePortForwardingRuleDao.java | 31 + .../dao/UsagePortForwardingRuleDaoImpl.java | 169 + .../usage/dao/UsageSecurityGroupDao.java | 29 + .../usage/dao/UsageSecurityGroupDaoImpl.java | 151 + .../com/cloud/usage/dao/UsageStorageDao.java | 35 + .../cloud/usage/dao/UsageStorageDaoImpl.java | 217 + .../cloud/usage/dao/UsageVMInstanceDao.java | 31 + .../usage/dao/UsageVMInstanceDaoImpl.java | 150 + .../cloud/usage/dao/UsageVMSnapshotDao.java | 31 + .../usage/dao/UsageVMSnapshotDaoImpl.java | 173 + .../com/cloud/usage/dao/UsageVPNUserDao.java | 29 + .../cloud/usage/dao/UsageVPNUserDaoImpl.java | 149 + .../com/cloud/usage/dao/UsageVmDiskDao.java | 31 + .../cloud/usage/dao/UsageVmDiskDaoImpl.java | 140 + .../com/cloud/usage/dao/UsageVolumeDao.java | 31 + .../cloud/usage/dao/UsageVolumeDaoImpl.java | 179 + .../java/com/cloud/user/AccountDetailVO.java | 77 + .../com/cloud/user/AccountDetailsDao.java | 37 + .../com/cloud/user/AccountDetailsDaoImpl.java | 104 + .../main/java/com/cloud/user/AccountVO.java | 194 + .../java/com/cloud/user/SSHKeyPairVO.java | 121 + .../java/com/cloud/user/UserAccountVO.java | 310 + .../java/com/cloud/user/UserStatisticsVO.java | 165 + .../java/com/cloud/user/UserStatsLogVO.java | 132 + .../src/main/java/com/cloud/user/UserVO.java | 296 + .../com/cloud/user/VmDiskStatisticsVO.java | 216 + .../java/com/cloud/user/dao/AccountDao.java | 75 + .../com/cloud/user/dao/AccountDaoImpl.java | 295 + .../com/cloud/user/dao/SSHKeyPairDao.java | 40 + .../com/cloud/user/dao/SSHKeyPairDaoImpl.java | 91 + .../com/cloud/user/dao/UserAccountDao.java | 33 + .../cloud/user/dao/UserAccountDaoImpl.java | 79 + .../main/java/com/cloud/user/dao/UserDao.java | 54 + .../java/com/cloud/user/dao/UserDaoImpl.java | 128 + .../com/cloud/user/dao/UserStatisticsDao.java | 35 + .../cloud/user/dao/UserStatisticsDaoImpl.java | 135 + .../com/cloud/user/dao/UserStatsLogDao.java | 23 + .../cloud/user/dao/UserStatsLogDaoImpl.java | 28 + .../cloud/user/dao/VmDiskStatisticsDao.java | 35 + .../user/dao/VmDiskStatisticsDaoImpl.java | 130 + .../java/com/cloud/vm/ConsoleProxyVO.java | 152 + .../java/com/cloud/vm/DomainRouterVO.java | 196 + .../com/cloud/vm/InstanceGroupVMMapVO.java | 68 + .../java/com/cloud/vm/InstanceGroupVO.java | 124 + .../src/main/java/com/cloud/vm/ItWorkDao.java | 45 + .../main/java/com/cloud/vm/ItWorkDaoImpl.java | 105 + .../src/main/java/com/cloud/vm/ItWorkVO.java | 179 + .../main/java/com/cloud/vm/NicDetailVO.java | 82 + .../src/main/java/com/cloud/vm/NicVO.java | 374 + .../com/cloud/vm/SecondaryStorageVmVO.java | 136 + .../com/cloud/vm/UserVmCloneSettingVO.java | 49 + .../java/com/cloud/vm/UserVmDetailVO.java | 83 + .../src/main/java/com/cloud/vm/UserVmVO.java | 128 + .../main/java/com/cloud/vm/VMInstanceVO.java | 570 + .../com/cloud/vm/dao/ConsoleProxyDao.java | 57 + .../com/cloud/vm/dao/ConsoleProxyDaoImpl.java | 336 + .../com/cloud/vm/dao/DomainRouterDao.java | 159 + .../com/cloud/vm/dao/DomainRouterDaoImpl.java | 435 + .../com/cloud/vm/dao/InstanceGroupDao.java | 38 + .../cloud/vm/dao/InstanceGroupDaoImpl.java | 77 + .../cloud/vm/dao/InstanceGroupVMMapDao.java | 30 + .../vm/dao/InstanceGroupVMMapDaoImpl.java | 72 + .../main/java/com/cloud/vm/dao/NicDao.java | 77 + .../java/com/cloud/vm/dao/NicDaoImpl.java | 305 + .../java/com/cloud/vm/dao/NicDetailsDao.java | 25 + .../com/cloud/vm/dao/NicDetailsDaoImpl.java | 31 + .../java/com/cloud/vm/dao/NicIpAliasDao.java | 61 + .../com/cloud/vm/dao/NicIpAliasDaoImpl.java | 174 + .../java/com/cloud/vm/dao/NicIpAliasVO.java | 239 + .../com/cloud/vm/dao/NicSecondaryIpDao.java | 56 + .../cloud/vm/dao/NicSecondaryIpDaoImpl.java | 148 + .../com/cloud/vm/dao/NicSecondaryIpVO.java | 132 + .../cloud/vm/dao/SecondaryStorageVmDao.java | 45 + .../vm/dao/SecondaryStorageVmDaoImpl.java | 271 + .../cloud/vm/dao/UserVmCloneSettingDao.java | 37 + .../vm/dao/UserVmCloneSettingDaoImpl.java | 71 + .../main/java/com/cloud/vm/dao/UserVmDao.java | 87 + .../java/com/cloud/vm/dao/UserVmDaoImpl.java | 664 + .../java/com/cloud/vm/dao/UserVmData.java | 759 + .../com/cloud/vm/dao/UserVmDetailsDao.java | 25 + .../cloud/vm/dao/UserVmDetailsDaoImpl.java | 32 + .../java/com/cloud/vm/dao/VMInstanceDao.java | 143 + .../com/cloud/vm/dao/VMInstanceDaoImpl.java | 846 + .../vm/snapshot/VMSnapshotDetailsVO.java | 84 + .../com/cloud/vm/snapshot/VMSnapshotVO.java | 251 + .../cloud/vm/snapshot/dao/VMSnapshotDao.java | 41 + .../vm/snapshot/dao/VMSnapshotDaoImpl.java | 185 + .../vm/snapshot/dao/VMSnapshotDetailsDao.java | 27 + .../dao/VMSnapshotDetailsDaoImpl.java | 31 + .../affinity/AffinityGroupDomainMapVO.java | 74 + .../affinity/AffinityGroupVMMapVO.java | 63 + .../cloudstack/affinity/AffinityGroupVO.java | 132 + .../affinity/dao/AffinityGroupDao.java | 41 + .../affinity/dao/AffinityGroupDaoImpl.java | 161 + .../dao/AffinityGroupDomainMapDao.java | 31 + .../dao/AffinityGroupDomainMapDaoImpl.java | 65 + .../affinity/dao/AffinityGroupVMMapDao.java | 48 + .../dao/AffinityGroupVMMapDaoImpl.java | 173 + .../cloud/entity/api/db/VMComputeTagVO.java | 68 + .../cloud/entity/api/db/VMEntityVO.java | 554 + .../cloud/entity/api/db/VMNetworkMapVO.java | 68 + .../cloud/entity/api/db/VMReservationVO.java | 138 + .../cloud/entity/api/db/VMRootDiskTagVO.java | 68 + .../entity/api/db/VolumeReservationVO.java | 84 + .../entity/api/db/dao/VMComputeTagDao.java | 31 + .../api/db/dao/VMComputeTagDaoImpl.java | 84 + .../cloud/entity/api/db/dao/VMEntityDao.java | 30 + .../entity/api/db/dao/VMEntityDaoImpl.java | 161 + .../entity/api/db/dao/VMNetworkMapDao.java | 33 + .../api/db/dao/VMNetworkMapDaoImpl.java | 101 + .../entity/api/db/dao/VMReservationDao.java | 31 + .../api/db/dao/VMReservationDaoImpl.java | 107 + .../entity/api/db/dao/VMRootDiskTagDao.java | 31 + .../api/db/dao/VMRootDiskTagDaoImpl.java | 82 + .../api/db/dao/VolumeReservationDao.java | 31 + .../api/db/dao/VolumeReservationDaoImpl.java | 62 + .../lb/ApplicationLoadBalancerRuleVO.java | 140 + .../dao/ApplicationLoadBalancerRuleDao.java | 43 + .../ApplicationLoadBalancerRuleDaoImpl.java | 147 + .../cloudstack/region/PortableIpDao.java | 36 + .../cloudstack/region/PortableIpDaoImpl.java | 110 + .../cloudstack/region/PortableIpRangeDao.java | 27 + .../region/PortableIpRangeDaoImpl.java | 44 + .../cloudstack/region/PortableIpRangeVO.java | 114 + .../cloudstack/region/PortableIpVO.java | 225 + .../cloudstack/region/RegionSyncVO.java | 96 + .../apache/cloudstack/region/RegionVO.java | 117 + .../cloudstack/region/dao/RegionDao.java | 28 + .../cloudstack/region/dao/RegionDaoImpl.java | 51 + .../gslb/GlobalLoadBalancerDaoImpl.java | 69 + .../gslb/GlobalLoadBalancerLbRuleMapDao.java | 30 + .../GlobalLoadBalancerLbRuleMapDaoImpl.java | 62 + .../gslb/GlobalLoadBalancerLbRuleMapVO.java | 96 + .../gslb/GlobalLoadBalancerRuleDao.java | 31 + .../region/gslb/GlobalLoadBalancerRuleVO.java | 195 + .../AutoScaleVmGroupDetailVO.java | 82 + .../AutoScaleVmProfileDetailVO.java | 82 + .../resourcedetail/DiskOfferingDetailVO.java | 82 + .../resourcedetail/FirewallRuleDetailVO.java | 82 + .../LBHealthCheckPolicyDetailVO.java | 78 + .../LBStickinessPolicyDetailVO.java | 82 + .../NetworkACLItemDetailVO.java | 82 + .../NetworkACLListDetailVO.java | 82 + .../RemoteAccessVpnDetailVO.java | 82 + .../resourcedetail/ResourceDetailsDao.java | 80 + .../ResourceDetailsDaoBase.java | 156 + .../Site2SiteCustomerGatewayDetailVO.java | 82 + .../Site2SiteVpnConnectionDetailVO.java | 82 + .../Site2SiteVpnGatewayDetailVO.java | 85 + .../SnapshotPolicyDetailVO.java | 81 + .../resourcedetail/UserDetailVO.java | 81 + .../resourcedetail/UserIpAddressDetailVO.java | 82 + .../resourcedetail/VpcDetailVO.java | 82 + .../resourcedetail/VpcGatewayDetailVO.java | 82 + .../dao/AutoScaleVmGroupDetailsDao.java | 26 + .../dao/AutoScaleVmGroupDetailsDaoImpl.java | 31 + .../dao/AutoScaleVmProfileDetailsDao.java | 26 + .../dao/AutoScaleVmProfileDetailsDaoImpl.java | 31 + .../dao/DiskOfferingDetailsDao.java | 26 + .../dao/DiskOfferingDetailsDaoImpl.java | 31 + .../dao/FirewallRuleDetailsDao.java | 26 + .../dao/FirewallRuleDetailsDaoImpl.java | 30 + .../dao/LBHealthCheckPolicyDetailsDao.java | 22 + .../LBHealthCheckPolicyDetailsDaoImpl.java | 27 + .../dao/LBStickinessPolicyDetailsDao.java | 22 + .../dao/LBStickinessPolicyDetailsDaoImpl.java | 27 + .../dao/NetworkACLItemDetailsDao.java | 26 + .../dao/NetworkACLItemDetailsDaoImpl.java | 30 + .../dao/NetworkACLListDetailsDao.java | 26 + .../dao/NetworkACLListDetailsDaoImpl.java | 30 + .../dao/RemoteAccessVpnDetailsDao.java | 26 + .../dao/RemoteAccessVpnDetailsDaoImpl.java | 30 + .../Site2SiteCustomerGatewayDetailsDao.java | 26 + ...ite2SiteCustomerGatewayDetailsDaoImpl.java | 30 + .../dao/Site2SiteVpnConnectionDetailsDao.java | 26 + .../Site2SiteVpnConnectionDetailsDaoImpl.java | 30 + .../dao/Site2SiteVpnGatewayDetailsDao.java | 27 + .../Site2SiteVpnGatewayDetailsDaoImpl.java | 30 + .../dao/SnapshotPolicyDetailsDao.java | 25 + .../dao/SnapshotPolicyDetailsDaoImpl.java | 30 + .../resourcedetail/dao/UserDetailsDao.java | 26 + .../dao/UserDetailsDaoImpl.java | 31 + .../dao/UserIpAddressDetailsDao.java | 26 + .../dao/UserIpAddressDetailsDaoImpl.java | 30 + .../resourcedetail/dao/VpcDetailsDao.java | 26 + .../resourcedetail/dao/VpcDetailsDaoImpl.java | 30 + .../dao/VpcGatewayDetailsDao.java | 26 + .../dao/VpcGatewayDetailsDaoImpl.java | 30 + .../storage/datastore/db/ImageStoreDao.java | 41 + .../datastore/db/ImageStoreDetailVO.java | 83 + .../datastore/db/ImageStoreDetailsDao.java | 30 + .../storage/datastore/db/ImageStoreVO.java | 203 + .../datastore/db/PrimaryDataStoreDao.java | 122 + .../datastore/db/PrimaryDataStoreDaoImpl.java | 525 + .../db/PrimaryDataStoreDetailVO.java | 82 + .../db/PrimaryDataStoreDetailsDao.java | 22 + .../datastore/db/SnapshotDataStoreDao.java | 66 + .../datastore/db/SnapshotDataStoreVO.java | 294 + .../datastore/db/StoragePoolDetailVO.java | 82 + .../datastore/db/StoragePoolDetailsDao.java | 24 + .../storage/datastore/db/StoragePoolVO.java | 381 + .../datastore/db/TemplateDataStoreDao.java | 86 + .../datastore/db/TemplateDataStoreVO.java | 396 + .../datastore/db/VolumeDataStoreDao.java | 54 + .../datastore/db/VolumeDataStoreVO.java | 384 + ...spring-engine-schema-core-daos-context.xml | 348 + ...-engine-schema-system-checkers-context.xml | 34 + .../dao/BareMetalRemovalUpdaterTest.java | 323 + .../upgrade/dao/DatabaseAccessObjectTest.java | 453 + .../cloud/upgrade/dao/DbUpgradeUtilsTest.java | 162 + cosmic-core/engine/storage/cache/pom.xml | 23 + .../allocator/StorageCacheAllocator.java | 29 + .../StorageCacheRandomAllocator.java | 93 + .../manager/StorageCacheManagerImpl.java | 379 + .../StorageCacheReplacementAlgorithm.java | 26 + .../StorageCacheReplacementAlgorithmLRU.java | 116 + ...ring-engine-storage-cache-core-context.xml | 38 + cosmic-core/engine/storage/datamotion/pom.xml | 30 + .../motion/AncientDataMotionStrategy.java | 579 + .../storage/motion/DataMotionServiceImpl.java | 91 + .../StorageSystemDataMotionStrategy.java | 430 + ...engine-storage-datamotion-core-context.xml | 34 + ...ine-storage-datamotion-storage-context.xml | 35 + cosmic-core/engine/storage/image/pom.xml | 18 + .../image/TemplateDataFactoryImpl.java | 157 + .../storage/image/TemplateServiceImpl.java | 875 + .../ImageStoreProviderManagerImpl.java | 162 + .../storage/image/store/ImageStoreImpl.java | 213 + .../storage/image/store/TemplateObject.java | 481 + .../store/lifecycle/ImageStoreLifeCycle.java | 24 + ...ring-engine-storage-image-core-context.xml | 45 + cosmic-core/engine/storage/pom.xml | 43 + cosmic-core/engine/storage/snapshot/pom.xml | 56 + .../snapshot/SnapshotDataFactoryImpl.java | 109 + .../storage/snapshot/SnapshotObject.java | 398 + .../storage/snapshot/SnapshotServiceImpl.java | 550 + .../snapshot/SnapshotStateMachineManager.java | 26 + .../SnapshotStateMachineManagerImpl.java | 62 + .../snapshot/SnapshotStrategyBase.java | 43 + .../StorageSystemSnapshotStrategy.java | 431 + .../snapshot/XenserverSnapshotStrategy.java | 418 + .../vmsnapshot/DefaultVMSnapshotStrategy.java | 395 + ...g-engine-storage-snapshot-core-context.xml | 41 + ...ngine-storage-snapshot-storage-context.xml | 39 + .../java/src/SnapshotDataFactoryTest.java | 42 + .../test/java/src/VMSnapshotStrategyTest.java | 311 + .../resources/SnapshotManagerTestContext.xml | 42 + .../snapshot/src/test/resources/db.properties | 50 + .../apache/cloudstack/storage/BaseType.java | 51 + .../apache/cloudstack/storage/EndPoint.java | 16 + .../cloudstack/storage/LocalHostEndpoint.java | 128 + .../storage/RemoteHostEndPoint.java | 215 + .../AbstractStoragePoolAllocator.java | 235 + .../ClusterScopeStoragePoolAllocator.java | 137 + ...GarbageCollectingStoragePoolAllocator.java | 92 + .../allocator/LocalStoragePoolAllocator.java | 148 + .../allocator/UseLocalForRootAllocator.java | 58 + .../ZoneWideStoragePoolAllocator.java | 137 + .../backup/SnapshotOnBackupStoreInfo.java | 25 + .../backup/datastore/BackupStoreInfo.java | 25 + .../storage/datastore/DataObjectManager.java | 41 + .../datastore/DataObjectManagerImpl.java | 383 + .../storage/datastore/DataStore.java | 16 + .../datastore/DataStoreManagerImpl.java | 132 + .../datastore/ObjectInDataStoreManager.java | 45 + .../ObjectInDataStoreManagerImpl.java | 394 + .../PrimaryDataStoreProviderManager.java | 33 + .../datastore/TemplateInDataStore.java | 16 + .../datastore/protocol/DataStoreProtocol.java | 32 + .../DataStoreProviderManagerImpl.java | 225 + .../storage/db/ObjectInDataStoreDao.java | 28 + .../storage/db/ObjectInDataStoreDaoImpl.java | 111 + .../storage/db/ObjectInDataStoreVO.java | 202 + .../endpoint/DefaultEndPointSelector.java | 392 + .../storage/helper/HypervisorHelper.java | 35 + .../storage/helper/HypervisorHelperImpl.java | 170 + .../helper/StorageStrategyFactoryImpl.java | 136 + .../storage/helper/VMSnapshotHelperImpl.java | 151 + .../image/BaseImageStoreDriverImpl.java | 292 + .../storage/image/ImageStoreDriver.java | 32 + .../storage/image/TemplateEntityImpl.java | 310 + .../image/datastore/ImageStoreHelper.java | 169 + .../datastore/ImageStoreProviderManager.java | 46 + .../storage/image/db/ImageStoreDaoImpl.java | 128 + .../image/db/ImageStoreDetailsDaoImpl.java | 92 + .../image/db/SnapshotDataStoreDaoImpl.java | 411 + .../image/db/TemplateDataStoreDaoImpl.java | 553 + .../image/db/VolumeDataStoreDaoImpl.java | 354 + .../storage/image/format/BAREMETAL.java | 32 + .../cloudstack/storage/image/format/ISO.java | 30 + .../storage/image/format/ImageFormat.java | 23 + .../image/format/ImageFormatHelper.java | 49 + .../cloudstack/storage/image/format/OVA.java | 32 + .../storage/image/format/QCOW2.java | 32 + .../cloudstack/storage/image/format/RAW.java | 32 + .../storage/image/format/Unknown.java | 32 + .../cloudstack/storage/image/format/VHD.java | 32 + .../cloudstack/storage/image/format/VHDX.java | 32 + .../image/motion/ImageMotionService.java | 29 + .../storage/snapshot/SnapshotEntityImpl.java | 191 + .../storage/vmsnapshot/VMSnapshotHelper.java | 38 + .../TemplateOnPrimaryDataStoreInfo.java | 32 + .../storage/volume/VolumeEvent.java | 34 + .../datastore/PrimaryDataStoreHelper.java | 243 + .../db/PrimaryDataStoreDetailsDaoImpl.java | 32 + .../db/TemplatePrimaryDataStoreDao.java | 31 + .../db/TemplatePrimaryDataStoreDaoImpl.java | 123 + .../volume/db/TemplatePrimaryDataStoreVO.java | 262 + .../spring-engine-storage-core-context.xml | 67 + .../storage-allocator/module.properties | 18 + ...gine-storage-storage-allocator-context.xml | 49 + .../api/storage/StrategyPriorityTest.java | 160 + .../cloudstack/storage/BaseTypeTest.java | 48 + cosmic-core/engine/storage/storage.ucls | 365 + cosmic-core/engine/storage/volume/pom.xml | 36 + .../datastore/PrimaryDataStoreImpl.java | 413 + .../PrimaryDataStoreProviderManagerImpl.java | 87 + .../storage/datastore/manager/data model.ucls | 75 + .../provider/DefaultHostListener.java | 112 + .../storage/datastore/type/DataStoreType.java | 23 + .../storage/datastore/type/ISCSI.java | 32 + .../datastore/type/NetworkFileSystem.java | 32 + .../storage/datastore/type/SharedMount.java | 30 + .../storage/volume/VolumeDataFactoryImpl.java | 124 + .../storage/volume/VolumeObject.java | 689 + .../storage/volume/VolumeServiceImpl.java | 1625 ++ ...ing-engine-storage-volume-core-context.xml | 49 + .../storage/volume/VolumeObjectTest.java | 77 + .../storage/volume/test/ConfiguratorTest.java | 92 + .../storage/volume/test/Server.java | 43 + .../storage/volume/test/Server1.java | 27 + .../volume/test/TestConfiguration.java | 40 + .../volume/test/TestInProcessAsync.java | 41 + .../volume/src/test/resources/testContext.xml | 79 + cosmic-core/framework/cluster/pom.xml | 35 + .../cloud/cluster/ActiveFencingException.java | 29 + .../cloud/cluster/ClusterFenceManager.java | 23 + .../cluster/ClusterFenceManagerImpl.java | 57 + .../ClusterInvalidSessionException.java | 30 + .../com/cloud/cluster/ClusterManager.java | 70 + .../com/cloud/cluster/ClusterManagerImpl.java | 1213 ++ .../cloud/cluster/ClusterManagerListener.java | 27 + .../cloud/cluster/ClusterManagerMBean.java | 31 + .../cluster/ClusterManagerMBeanImpl.java | 67 + .../cloud/cluster/ClusterManagerMessage.java | 46 + .../cluster/ClusterNodeJoinEventArgs.java | 43 + .../cluster/ClusterNodeLeftEventArgs.java | 43 + .../com/cloud/cluster/ClusterService.java | 26 + .../cloud/cluster/ClusterServiceAdapter.java | 34 + .../com/cloud/cluster/ClusterServicePdu.java | 112 + .../cluster/ClusterServiceRequestPdu.java | 54 + .../cluster/ClusterServiceServletAdapter.java | 133 + .../ClusterServiceServletContainer.java | 184 + .../ClusterServiceServletHttpHandler.java | 192 + .../cluster/ClusterServiceServletImpl.java | 138 + .../cloud/cluster/ManagementServerHost.java | 33 + .../cluster/ManagementServerHostPeerVO.java | 119 + .../cloud/cluster/ManagementServerHostVO.java | 183 + .../cloud/cluster/RemoteMethodConstants.java | 23 + .../cluster/dao/ManagementServerHostDao.java | 53 + .../dao/ManagementServerHostDaoImpl.java | 275 + .../dao/ManagementServerHostPeerDao.java | 29 + .../dao/ManagementServerHostPeerDaoImpl.java | 104 + .../spring-framework-cluster-core-context.xml | 36 + .../ClusterServiceServletAdapterTest.java | 47 + cosmic-core/framework/config/pom.xml | 23 + .../cloudstack/config/Configuration.java | 84 + .../framework/config/ConfigDepot.java | 34 + .../framework/config/ConfigDepotAdmin.java | 38 + .../framework/config/ConfigKey.java | 194 + .../framework/config/Configurable.java | 40 + .../framework/config/ScopedConfigStorage.java | 30 + .../config/dao/ConfigurationDao.java | 70 + .../config/dao/ConfigurationDaoImpl.java | 214 + .../config/impl/ConfigDepotImpl.java | 211 + .../config/impl/ConfigurationVO.java | 188 + ...work-config-system-context-inheritable.xml | 38 + ...spring-framework-config-system-context.xml | 51 + .../framework/config/ConfigKeyTest.java | 48 + .../config/impl/ConfigDepotAdminTest.java | 100 + cosmic-core/framework/db/pom.xml | 56 + .../java/com/cloud/dao/EntityManagerImpl.java | 125 + .../crypt/EncryptionSecretKeyChanger.java | 371 + .../java/com/cloud/utils/db/Attribute.java | 254 + .../cloud/utils/db/ConnectionConcierge.java | 217 + .../utils/db/ConnectionConciergeMBean.java | 30 + .../src/main/java/com/cloud/utils/db/DB.java | 43 + .../main/java/com/cloud/utils/db/DbUtil.java | 286 + .../main/java/com/cloud/utils/db/EcInfo.java | 107 + .../main/java/com/cloud/utils/db/Encrypt.java | 32 + .../main/java/com/cloud/utils/db/Filter.java | 115 + .../java/com/cloud/utils/db/GenericDao.java | 279 + .../com/cloud/utils/db/GenericDaoBase.java | 2016 +++ .../cloud/utils/db/GenericQueryBuilder.java | 176 + .../cloud/utils/db/GenericSearchBuilder.java | 248 + .../java/com/cloud/utils/db/GlobalLock.java | 239 + .../main/java/com/cloud/utils/db/GroupBy.java | 113 + .../java/com/cloud/utils/db/JoinBuilder.java | 75 + .../java/com/cloud/utils/db/JoinType.java | 32 + .../java/com/cloud/utils/db/Merovingian2.java | 416 + .../com/cloud/utils/db/MerovingianMBean.java | 31 + .../java/com/cloud/utils/db/QueryBuilder.java | 29 + .../java/com/cloud/utils/db/ScriptRunner.java | 226 + .../java/com/cloud/utils/db/SearchBase.java | 502 + .../com/cloud/utils/db/SearchBuilder.java | 61 + .../com/cloud/utils/db/SearchCriteria.java | 315 + .../com/cloud/utils/db/SequenceFetcher.java | 164 + .../java/com/cloud/utils/db/SqlGenerator.java | 672 + .../java/com/cloud/utils/db/StateMachine.java | 31 + .../java/com/cloud/utils/db/Transaction.java | 80 + .../cloud/utils/db/TransactionAttachment.java | 34 + .../cloud/utils/db/TransactionCallback.java | 25 + .../utils/db/TransactionCallbackNoReturn.java | 31 + .../db/TransactionCallbackWithException.java | 25 + ...nsactionCallbackWithExceptionNoReturn.java | 31 + .../utils/db/TransactionContextBuilder.java | 65 + .../db/TransactionContextInterceptor.java | 40 + .../utils/db/TransactionContextListener.java | 41 + .../com/cloud/utils/db/TransactionLegacy.java | 1166 ++ .../com/cloud/utils/db/TransactionMBean.java | 33 + .../cloud/utils/db/TransactionMBeanImpl.java | 113 + .../com/cloud/utils/db/TransactionStatus.java | 25 + .../com/cloud/utils/db/UpdateBuilder.java | 147 + .../java/com/cloud/utils/db/UpdateFilter.java | 29 + .../spring-framework-db-system-context.xml | 32 + .../test/java/com/cloud/utils/DbUtilTest.java | 253 + .../com/cloud/utils/db/DbAnnotatedBase.java | 45 + .../utils/db/DbAnnotatedBaseDerived.java | 27 + .../java/com/cloud/utils/db/DbTestDao.java | 62 + .../java/com/cloud/utils/db/DbTestUtils.java | 71 + .../java/com/cloud/utils/db/DbTestVO.java | 56 + .../com/cloud/utils/db/DummyComponent.java | 27 + .../cloud/utils/db/ElementCollectionTest.java | 73 + .../java/com/cloud/utils/db/FilterTest.java | 44 + .../cloud/utils/db/GenericDaoBaseTest.java | 151 + .../com/cloud/utils/db/GlobalLockTest.java | 85 + .../java/com/cloud/utils/db/GroupByTest.java | 81 + .../com/cloud/utils/db/Merovingian2Test.java | 80 + .../com/cloud/utils/db/TestTransaction.java | 130 + .../db/TransactionContextBuilderTest.java | 62 + .../com/cloud/utils/db/TransactionTest.java | 166 + .../db/src/test/resources/db.properties | 53 + cosmic-core/framework/events/pom.xml | 26 + .../cloudstack/framework/events/Event.java | 93 + .../cloudstack/framework/events/EventBus.java | 53 + .../framework/events/EventBusException.java | 30 + .../framework/events/EventSubscriber.java | 28 + .../framework/events/EventTopic.java | 57 + cosmic-core/framework/ipc/pom.xml | 49 + .../com/cloud/agent/manager/Commands.java | 153 + .../framework/async/AsyncCallFuture.java | 82 + .../async/AsyncCallbackDispatcher.java | 160 + .../framework/async/AsyncCallbackDriver.java | 23 + .../async/AsyncCompletionCallback.java | 23 + .../framework/async/AsyncRpcContext.java | 31 + .../async/InplaceAsyncCallbackDriver.java | 27 + .../cloudstack/framework/async/Void.java | 27 + .../framework/client/ClientMessageBus.java | 30 + .../client/ClientTransportConnection.java | 78 + .../client/ClientTransportEndpoint.java | 39 + .../client/ClientTransportEndpointSite.java | 47 + .../client/ClientTransportProvider.java | 144 + .../framework/messagebus/MessageBus.java | 38 + .../framework/messagebus/MessageBusBase.java | 449 + .../messagebus/MessageBusEndpoint.java | 60 + .../framework/messagebus/MessageDetector.java | 92 + .../messagebus/MessageDispatcher.java | 150 + .../framework/messagebus/MessageHandler.java | 30 + .../messagebus/MessageSubscriber.java | 24 + .../framework/messagebus/PublishScope.java | 24 + .../framework/rpc/RpcCallRequestPdu.java | 68 + .../framework/rpc/RpcCallResponsePdu.java | 80 + .../framework/rpc/RpcCallbackDispatcher.java | 73 + .../framework/rpc/RpcCallbackListener.java | 25 + .../framework/rpc/RpcClientCall.java | 52 + .../framework/rpc/RpcClientCallImpl.java | 231 + .../framework/rpc/RpcException.java | 35 + .../framework/rpc/RpcIOException.java | 36 + .../cloudstack/framework/rpc/RpcProvider.java | 52 + .../framework/rpc/RpcProviderImpl.java | 246 + .../framework/rpc/RpcServerCall.java | 28 + .../framework/rpc/RpcServerCallImpl.java | 69 + .../framework/rpc/RpcServiceDispatcher.java | 117 + .../framework/rpc/RpcServiceEndpoint.java | 30 + .../framework/rpc/RpcServiceHandler.java | 30 + .../framework/rpc/RpcTimeoutException.java | 32 + .../serializer/JsonMessageSerializer.java | 86 + .../serializer/MessageSerializer.java | 25 + .../serializer/OnwireClassRegistry.java | 187 + .../framework/serializer/OnwireName.java | 31 + .../framework/server/ServerMessageBus.java | 30 + .../server/ServerTransportProvider.java | 192 + .../framework/transport/TransportAddress.java | 138 + .../transport/TransportAddressMapper.java | 23 + .../transport/TransportAttachRequestPdu.java | 34 + .../transport/TransportAttachResponsePdu.java | 43 + .../transport/TransportConnectRequestPdu.java | 46 + .../TransportConnectResponsePdu.java | 37 + .../framework/transport/TransportDataPdu.java | 47 + .../transport/TransportEndpoint.java | 25 + .../transport/TransportEndpointSite.java | 143 + .../transport/TransportMultiplexier.java | 23 + .../framework/transport/TransportPdu.java | 43 + .../transport/TransportProvider.java | 35 + .../spring-framework-ipc-core-context.xml | 59 + .../codestyle/AsyncSampleCallee.java | 39 + .../AsyncSampleEventDrivenStyleCaller.java | 121 + .../AsyncSampleListenerStyleCaller.java | 36 + .../sampleserver/SampleManagementServer.java | 36 + .../SampleManagementServerApp.java | 56 + .../sampleserver/SampleManagerComponent.java | 101 + .../sampleserver/SampleManagerComponent2.java | 75 + .../SampleStoragePrepareAnswer.java | 37 + .../SampleStoragePrepareCommand.java | 47 + .../cloudstack/messagebus/TestMessageBus.java | 157 + .../test/resources/MessageBusTestContext.xml | 51 + .../SampleManagementServerAppContext.xml | 62 + .../ipc/src/test/resources/log4j-cloud.xml | 94 + cosmic-core/framework/jobs/pom.xml | 58 + .../cloudstack/framework/jobs/AsyncJob.java | 118 + .../framework/jobs/AsyncJobDispatcher.java | 28 + .../jobs/AsyncJobExecutionContext.java | 219 + .../framework/jobs/AsyncJobMBean.java | 53 + .../framework/jobs/AsyncJobManager.java | 134 + .../jobs/JobCancellationException.java | 46 + .../cloudstack/framework/jobs/Outcome.java | 62 + .../framework/jobs/dao/AsyncJobDao.java | 46 + .../framework/jobs/dao/AsyncJobDaoImpl.java | 229 + .../jobs/dao/AsyncJobJoinMapDao.java | 47 + .../jobs/dao/AsyncJobJoinMapDaoImpl.java | 259 + .../jobs/dao/AsyncJobJournalDao.java | 27 + .../jobs/dao/AsyncJobJournalDaoImpl.java | 45 + .../framework/jobs/dao/SyncQueueDao.java | 27 + .../framework/jobs/dao/SyncQueueDaoImpl.java | 77 + .../framework/jobs/dao/SyncQueueItemDao.java | 36 + .../jobs/dao/SyncQueueItemDaoImpl.java | 172 + .../framework/jobs/dao/VmWorkJobDao.java | 40 + .../framework/jobs/dao/VmWorkJobDaoImpl.java | 203 + .../jobs/impl/AsyncJobJoinMapVO.java | 215 + .../jobs/impl/AsyncJobJournalVO.java | 110 + .../jobs/impl/AsyncJobMBeanImpl.java | 165 + .../jobs/impl/AsyncJobManagerImpl.java | 1079 ++ .../framework/jobs/impl/AsyncJobMonitor.java | 205 + .../framework/jobs/impl/AsyncJobVO.java | 398 + .../jobs/impl/JobSerializerHelper.java | 203 + .../framework/jobs/impl/OutcomeImpl.java | 125 + .../framework/jobs/impl/SyncQueueItem.java | 41 + .../framework/jobs/impl/SyncQueueItemVO.java | 144 + .../framework/jobs/impl/SyncQueueManager.java | 41 + .../jobs/impl/SyncQueueManagerImpl.java | 277 + .../framework/jobs/impl/SyncQueueVO.java | 139 + .../framework/jobs/impl/VmWorkJobVO.java | 92 + .../spring-framework-jobs-core-context.xml | 48 + .../framework/jobs/AsyncJobManagerTest.java | 131 + .../AsyncJobManagerTestConfiguration.java | 53 + .../framework/jobs/AsyncJobTestDashboard.java | 47 + .../jobs/AsyncJobTestDispatcher.java | 63 + .../resources/AsyncJobManagerTestContext.xml | 38 + .../jobs/src/test/resources/commonContext.xml | 37 + .../jobs/src/test/resources/db.properties | 47 + .../jobs/src/test/resources/log4j.properties | 35 + cosmic-core/framework/managed-context/pom.xml | 17 + .../AbstractManagedContextListener.java | 32 + .../managed/context/ManagedContext.java | 33 + .../context/ManagedContextListener.java | 35 + .../context/ManagedContextRunnable.java | 85 + .../context/ManagedContextTimerTask.java | 37 + .../managed/context/ManagedContextUtils.java | 55 + .../context/impl/DefaultManagedContext.java | 155 + .../threadlocal/ManagedThreadLocal.java | 81 + .../impl/DefaultManagedContextTest.java | 272 + cosmic-core/framework/pom.xml | 27 + cosmic-core/framework/rest/pom.xml | 59 + .../CSJacksonAnnotationIntrospector.java | 59 + .../ws/jackson/CSJacksonAnnotationModule.java | 45 + .../framework/ws/jackson/UriSerializer.java | 58 + .../framework/ws/jackson/UrisSerializer.java | 70 + .../cloudstack/framework/ws/jackson/Url.java | 53 + .../ws/jackson/CSJacksonAnnotationTest.java | 117 + cosmic-core/framework/security/pom.xml | 42 + .../framework/security/keys/KeysManager.java | 38 + .../security/keys/KeysManagerImpl.java | 129 + .../security/keystore/KeystoreDao.java | 33 + .../security/keystore/KeystoreDaoImpl.java | 127 + .../security/keystore/KeystoreManager.java | 71 + .../keystore/KeystoreManagerImpl.java | 160 + .../security/keystore/KeystoreVO.java | 105 + ...spring-framework-security-core-context.xml | 32 + .../framework/spring/lifecycle/pom.xml | 23 + .../lifecycle/AbstractBeanCollector.java | 113 + .../lifecycle/AbstractSmartLifeCycle.java | 53 + .../CloudStackExtendedLifeCycle.java | 167 + .../CloudStackExtendedLifeCycleStart.java | 49 + .../lifecycle/CloudStackLog4jSetup.java | 56 + .../lifecycle/ConfigDepotLifeCycle.java | 47 + .../lifecycle/registry/DumpRegistry.java | 77 + .../lifecycle/registry/ExtensionRegistry.java | 231 + .../registry/PluggableServiceLifecycle.java | 52 + .../lifecycle/registry/RegistryLifecycle.java | 186 + .../lifecycle/registry/RegistryUtils.java | 35 + cosmic-core/framework/spring/module/pom.xml | 32 + .../context/ResourceApplicationContext.java | 62 + .../factory/CloudStackSpringContext.java | 143 + .../factory/ModuleBasedContextFactory.java | 83 + .../module/factory/QuietLoaderFactory.java | 62 + .../locator/ModuleDefinitionLocator.java | 36 + .../ClasspathModuleDefinitionLocator.java | 63 + .../spring/module/model/ModuleDefinition.java | 48 + .../module/model/ModuleDefinitionSet.java | 36 + .../model/impl/DefaultModuleDefinition.java | 173 + .../impl/DefaultModuleDefinitionSet.java | 294 + .../cloudstack/spring/module/util/Main.java | 57 + .../module/util/ModuleLocationUtils.java | 53 + .../web/CloudStackContextLoaderListener.java | 76 + .../spring/module/web/ModuleBasedFilter.java | 60 + .../module/model/impl/defaults-context.xml | 37 + .../spring/module/factory/InitTest.java | 39 + .../ModuleBasedContextFactoryTest.java | 138 + ...asspathModuleDefinitionSetLocatorTest.java | 40 + .../impl/DefaultModuleDefinitionTest.java | 132 + .../testfiles/all/defaults.properties | 18 + .../all/empty-context-inheritable.xml | 26 + .../resources/testfiles/all/empty-context.xml | 26 + .../all/empty2-context-inheritable.xml | 26 + .../testfiles/all/empty2-context.xml | 26 + .../resources/testfiles/all/module.properties | 17 + .../testfiles/all/test2-defaults.properties | 17 + .../testfiles/badname/module.properties | 17 + .../testfiles/blankname/module.properties | 18 + .../testfiles/good/empty-context.xml | 26 + .../testfiles/good/module.properties | 17 + .../testfiles/missingname/module.properties | 17 + .../testfiles/wrongname/module.properties | 17 + .../testhierarchy/base/module.properties | 17 + .../base/test-context-inheritable.xml | 28 + .../testhierarchy/base/test-context.xml | 34 + .../testhierarchy/child1-1/module.properties | 18 + .../testhierarchy/child1-1/test-context.xml | 34 + .../testhierarchy/child1/module.properties | 18 + .../child1/test-context-override.xml | 30 + .../testhierarchy/child1/test-context.xml | 38 + .../testhierarchy/child2/module.properties | 18 + .../testhierarchy/child2/test-context.xml | 33 + .../testhierarchy/excluded/module.properties | 19 + .../testhierarchy/excluded/test-context.xml | 33 + .../testhierarchy/excluded2/module.properties | 19 + .../testhierarchy/excluded2/test-context.xml | 33 + .../orphan-of-excluded/defaults.properties | 20 + .../orphan-of-excluded/module.properties | 19 + .../orphan-of-excluded/test-context.xml | 33 + .../testhierarchy/orphan1/module.properties | 18 + .../testhierarchy/orphan1/test-context.xml | 30 + cosmic-core/nucleo/pom.xml | 93 + .../java/com/cloud/agent/IAgentControl.java | 34 + .../cloud/agent/IAgentControlListener.java | 32 + .../cloud/agent/StartupCommandProcessor.java | 41 + .../cloud/agent/api/AgentControlAnswer.java | 33 + .../cloud/agent/api/AgentControlCommand.java | 31 + .../com/cloud/agent/api/AttachIsoCommand.java | 62 + .../AttachOrDettachConfigDriveCommand.java | 58 + .../cloud/agent/api/BackupSnapshotAnswer.java | 46 + .../agent/api/BackupSnapshotCommand.java | 110 + .../agent/api/BumpUpPriorityCommand.java | 33 + .../com/cloud/agent/api/CancelCommand.java | 46 + .../cloud/agent/api/ChangeAgentAnswer.java | 29 + .../cloud/agent/api/ChangeAgentCommand.java | 49 + .../cloud/agent/api/CheckHealthAnswer.java | 30 + .../cloud/agent/api/CheckHealthCommand.java | 32 + .../cloud/agent/api/CheckNetworkAnswer.java | 42 + .../cloud/agent/api/CheckNetworkCommand.java | 45 + .../cloud/agent/api/CheckOnHostAnswer.java | 51 + .../cloud/agent/api/CheckOnHostCommand.java | 44 + .../cloud/agent/api/CheckRouterAnswer.java | 70 + .../cloud/agent/api/CheckRouterCommand.java | 38 + .../api/CheckS2SVpnConnectionsAnswer.java | 79 + .../api/CheckS2SVpnConnectionsCommand.java | 47 + .../com/cloud/agent/api/CheckStateAnswer.java | 49 + .../cloud/agent/api/CheckStateCommand.java | 44 + .../agent/api/CheckVirtualMachineAnswer.java | 53 + .../agent/api/CheckVirtualMachineCommand.java | 44 + .../agent/api/CleanupNetworkRulesCmd.java | 45 + .../api/ClusterVMMetaDataSyncAnswer.java | 55 + .../api/ClusterVMMetaDataSyncCommand.java | 50 + .../agent/api/ComputeChecksumCommand.java | 50 + .../ConsoleAccessAuthenticationAnswer.java | 91 + .../ConsoleAccessAuthenticationCommand.java | 71 + .../api/ConsoleProxyLoadReportCommand.java | 42 + ...atePrivateTemplateFromSnapshotCommand.java | 75 + ...reatePrivateTemplateFromVolumeCommand.java | 102 + .../agent/api/CreateStoragePoolCommand.java | 57 + .../agent/api/CreateVMSnapshotAnswer.java | 61 + .../agent/api/CreateVMSnapshotCommand.java | 38 + .../api/CreateVolumeFromSnapshotAnswer.java | 40 + .../api/CreateVolumeFromSnapshotCommand.java | 53 + .../api/CreateVolumeFromVMSnapshotAnswer.java | 53 + .../CreateVolumeFromVMSnapshotCommand.java | 89 + .../java/com/cloud/agent/api/CronCommand.java | 27 + .../agent/api/DeleteSnapshotsDirCommand.java | 53 + .../agent/api/DeleteStoragePoolCommand.java | 87 + .../agent/api/DeleteVMSnapshotAnswer.java | 49 + .../agent/api/DeleteVMSnapshotCommand.java | 30 + .../agent/api/DirectNetworkUsageAnswer.java | 59 + .../agent/api/DirectNetworkUsageCommand.java | 86 + .../ExternalNetworkResourceUsageAnswer.java | 44 + .../ExternalNetworkResourceUsageCommand.java | 31 + .../java/com/cloud/agent/api/FenceAnswer.java | 39 + .../com/cloud/agent/api/FenceCommand.java | 64 + .../cloud/agent/api/GetDomRVersionAnswer.java | 48 + .../cloud/agent/api/GetDomRVersionCmd.java | 38 + .../cloud/agent/api/GetFileStatsAnswer.java | 41 + .../cloud/agent/api/GetFileStatsCommand.java | 44 + .../cloud/agent/api/GetGPUStatsAnswer.java | 43 + .../cloud/agent/api/GetGPUStatsCommand.java | 50 + .../cloud/agent/api/GetHostStatsAnswer.java | 91 + .../cloud/agent/api/GetHostStatsCommand.java | 55 + .../agent/api/GetRouterAlertsAnswer.java | 60 + .../agent/api/GetStorageStatsAnswer.java | 53 + .../agent/api/GetStorageStatsCommand.java | 88 + .../cloud/agent/api/GetVmConfigAnswer.java | 76 + .../cloud/agent/api/GetVmConfigCommand.java | 49 + .../cloud/agent/api/GetVmDiskStatsAnswer.java | 50 + .../agent/api/GetVmDiskStatsCommand.java | 57 + .../agent/api/GetVmIpAddressCommand.java | 50 + .../com/cloud/agent/api/GetVmStatsAnswer.java | 43 + .../cloud/agent/api/GetVmStatsCommand.java | 57 + .../com/cloud/agent/api/GetVncPortAnswer.java | 51 + .../cloud/agent/api/GetVncPortCommand.java | 46 + .../com/cloud/agent/api/HostStatsEntry.java | 115 + .../com/cloud/agent/api/MaintainAnswer.java | 49 + .../com/cloud/agent/api/MaintainCommand.java | 31 + .../cloud/agent/api/ManageSnapshotAnswer.java | 43 + .../agent/api/ManageSnapshotCommand.java | 94 + .../com/cloud/agent/api/MigrateAnswer.java | 36 + .../com/cloud/agent/api/MigrateCommand.java | 71 + .../agent/api/MigrateWithStorageAnswer.java | 43 + .../agent/api/MigrateWithStorageCommand.java | 84 + .../api/MigrateWithStorageCompleteAnswer.java | 42 + .../MigrateWithStorageCompleteCommand.java | 39 + .../api/MigrateWithStorageReceiveAnswer.java | 61 + .../api/MigrateWithStorageReceiveCommand.java | 50 + .../api/MigrateWithStorageSendAnswer.java | 43 + .../api/MigrateWithStorageSendCommand.java | 63 + .../cloud/agent/api/ModifySshKeysCommand.java | 51 + .../agent/api/ModifyStoragePoolAnswer.java | 67 + .../agent/api/ModifyStoragePoolCommand.java | 76 + .../agent/api/ModifyVmNicConfigAnswer.java | 39 + .../agent/api/ModifyVmNicConfigCommand.java | 69 + .../api/NetworkRulesSystemVmCommand.java | 50 + .../api/NetworkRulesVmSecondaryIpCommand.java | 72 + .../cloud/agent/api/NetworkUsageAnswer.java | 63 + .../cloud/agent/api/NetworkUsageCommand.java | 97 + .../agent/api/PerformanceMonitorAnswer.java | 30 + .../agent/api/PerformanceMonitorCommand.java | 49 + .../java/com/cloud/agent/api/PingAnswer.java | 36 + .../java/com/cloud/agent/api/PingCommand.java | 70 + .../cloud/agent/api/PingRoutingCommand.java | 60 + .../api/PingRoutingWithNwGroupsCommand.java | 48 + .../agent/api/PingRoutingWithOvsCommand.java | 45 + .../cloud/agent/api/PingStorageCommand.java | 40 + .../com/cloud/agent/api/PingTestCommand.java | 58 + .../com/cloud/agent/api/PlugNicAnswer.java | 29 + .../com/cloud/agent/api/PlugNicCommand.java | 70 + .../agent/api/PrepareForMigrationAnswer.java | 37 + .../agent/api/PrepareForMigrationCommand.java | 42 + .../agent/api/PrepareOCFS2NodesCommand.java | 47 + .../api/PropagateResourceEventCommand.java | 51 + .../java/com/cloud/agent/api/ReadyAnswer.java | 34 + .../com/cloud/agent/api/ReadyCommand.java | 62 + .../com/cloud/agent/api/RebootAnswer.java | 45 + .../com/cloud/agent/api/RebootCommand.java | 47 + .../cloud/agent/api/RebootRouterCommand.java | 38 + .../api/RecurringNetworkUsageAnswer.java | 35 + .../api/RecurringNetworkUsageCommand.java | 39 + .../agent/api/RevertToVMSnapshotAnswer.java | 63 + .../agent/api/RevertToVMSnapshotCommand.java | 52 + .../com/cloud/agent/api/ScaleVmAnswer.java | 31 + .../com/cloud/agent/api/ScaleVmCommand.java | 125 + .../api/ScheduleHostScanTaskCommand.java | 35 + .../api/SecStorageFirewallCfgCommand.java | 90 + .../agent/api/SecStorageSetupAnswer.java | 37 + .../agent/api/SecStorageSetupCommand.java | 77 + .../agent/api/SecStorageVMSetupCommand.java | 60 + .../agent/api/SecurityGroupRuleAnswer.java | 70 + .../agent/api/SecurityGroupRulesCmd.java | 298 + .../java/com/cloud/agent/api/SetupAnswer.java | 43 + .../com/cloud/agent/api/SetupCommand.java | 63 + .../agent/api/SetupGuestNetworkCommand.java | 72 + .../com/cloud/agent/api/ShutdownCommand.java | 59 + .../com/cloud/agent/api/SnapshotCommand.java | 127 + .../java/com/cloud/agent/api/StartAnswer.java | 71 + .../com/cloud/agent/api/StartCommand.java | 67 + .../com/cloud/agent/api/StartupAnswer.java | 46 + .../com/cloud/agent/api/StartupCommand.java | 288 + .../agent/api/StartupExternalDhcpCommand.java | 28 + .../api/StartupExternalFirewallCommand.java | 29 + .../StartupExternalLoadBalancerCommand.java | 29 + .../cloud/agent/api/StartupProxyCommand.java | 53 + .../agent/api/StartupRoutingCommand.java | 183 + .../api/StartupSecondaryStorageCommand.java | 35 + .../agent/api/StartupStorageCommand.java | 111 + .../api/StartupTrafficMonitorCommand.java | 29 + .../agent/api/StartupVMMAgentCommand.java | 86 + .../java/com/cloud/agent/api/StopAnswer.java | 48 + .../java/com/cloud/agent/api/StopCommand.java | 89 + .../cloud/agent/api/TransferAgentCommand.java | 60 + .../com/cloud/agent/api/UnPlugNicAnswer.java | 29 + .../com/cloud/agent/api/UnPlugNicCommand.java | 48 + .../cloud/agent/api/UnregisterNicCommand.java | 58 + .../cloud/agent/api/UnregisterVMCommand.java | 46 + .../agent/api/UpdateHostPasswordCommand.java | 63 + .../agent/api/UpgradeSnapshotCommand.java | 58 + .../agent/api/VMSnapshotBaseCommand.java | 84 + .../com/cloud/agent/api/VMSnapshotTO.java | 126 + .../agent/api/ValidateSnapshotAnswer.java | 56 + .../agent/api/ValidateSnapshotCommand.java | 82 + .../com/cloud/agent/api/VmDiskStatsEntry.java | 97 + .../com/cloud/agent/api/VmStatsEntry.java | 136 + .../cloud/agent/api/check/CheckSshAnswer.java | 40 + .../agent/api/check/CheckSshCommand.java | 68 + .../proxy/CheckConsoleProxyLoadCommand.java | 62 + .../api/proxy/ConsoleProxyLoadAnswer.java | 47 + .../cloud/agent/api/proxy/ProxyCommand.java | 28 + ...rtConsoleProxyAgentHttpHandlerCommand.java | 71 + .../proxy/WatchConsoleProxyLoadCommand.java | 68 + .../routing/AggregationControlCommand.java | 46 + .../api/routing/CreateIpAliasCommand.java | 40 + .../CreateLoadBalancerApplianceCommand.java | 51 + .../api/routing/DeleteIpAliasCommand.java | 48 + .../DestroyLoadBalancerApplianceCommand.java | 35 + .../agent/api/routing/DhcpEntryCommand.java | 146 + .../api/routing/DnsMasqConfigCommand.java | 38 + .../api/routing/GetRouterAlertsCommand.java | 48 + .../GlobalLoadBalancerConfigAnswer.java | 30 + .../GlobalLoadBalancerConfigCommand.java | 101 + .../cloud/agent/api/routing/GroupAnswer.java | 42 + .../routing/HealthCheckLBConfigAnswer.java | 44 + .../routing/HealthCheckLBConfigCommand.java | 41 + .../cloud/agent/api/routing/IpAliasTO.java | 44 + .../agent/api/routing/IpAssocAnswer.java | 51 + .../agent/api/routing/IpAssocCommand.java | 48 + .../agent/api/routing/IpAssocVpcCommand.java | 38 + .../routing/LoadBalancerConfigCommand.java | 75 + .../api/routing/NetworkElementCommand.java | 76 + .../routing/RemoteAccessVpnCfgCommand.java | 112 + .../api/routing/SavePasswordCommand.java | 56 + .../api/routing/SetFirewallRulesAnswer.java | 39 + .../api/routing/SetFirewallRulesCommand.java | 103 + .../api/routing/SetMonitorServiceCommand.java | 58 + .../api/routing/SetNetworkACLAnswer.java | 39 + .../api/routing/SetNetworkACLCommand.java | 113 + .../routing/SetPortForwardingRulesAnswer.java | 41 + .../SetPortForwardingRulesCommand.java | 49 + .../SetPortForwardingRulesVpcCommand.java | 33 + .../agent/api/routing/SetSourceNatAnswer.java | 31 + .../api/routing/SetSourceNatCommand.java | 45 + .../api/routing/SetStaticNatRulesAnswer.java | 41 + .../api/routing/SetStaticNatRulesCommand.java | 55 + .../api/routing/SetStaticRouteAnswer.java | 46 + .../api/routing/SetStaticRouteCommand.java | 61 + .../api/routing/Site2SiteVpnCfgCommand.java | 177 + .../api/routing/SiteLoadBalancerConfig.java | 134 + .../agent/api/routing/UserDataCommand.java | 67 + .../agent/api/routing/VmDataCommand.java | 76 + .../agent/api/routing/VpnUsersCfgCommand.java | 107 + .../api/storage/AbstractDownloadCommand.java | 81 + .../api/storage/AbstractUploadCommand.java | 70 + .../agent/api/storage/CopyVolumeAnswer.java | 46 + .../agent/api/storage/CopyVolumeCommand.java | 78 + .../cloud/agent/api/storage/CreateAnswer.java | 58 + .../agent/api/storage/CreateCommand.java | 103 + .../CreateEntityDownloadURLAnswer.java | 40 + .../CreateEntityDownloadURLCommand.java | 86 + .../storage/CreatePrivateTemplateAnswer.java | 82 + .../storage/CreatePrivateTemplateCommand.java | 95 + .../DeleteEntityDownloadURLCommand.java | 75 + .../agent/api/storage/DestroyAnswer.java | 34 + .../agent/api/storage/DestroyCommand.java | 58 + .../agent/api/storage/DownloadAnswer.java | 149 + .../agent/api/storage/ListTemplateAnswer.java | 56 + .../api/storage/ListTemplateCommand.java | 50 + .../agent/api/storage/ListVolumeAnswer.java | 56 + .../agent/api/storage/ListVolumeCommand.java | 49 + .../ManageVolumeAvailabilityAnswer.java | 35 + .../ManageVolumeAvailabilityCommand.java | 56 + .../api/storage/MigrateVolumeAnswer.java | 50 + .../api/storage/MigrateVolumeCommand.java | 75 + .../storage/PrimaryStorageDownloadAnswer.java | 58 + .../PrimaryStorageDownloadCommand.java | 90 + .../agent/api/storage/ResizeVolumeAnswer.java | 43 + .../api/storage/ResizeVolumeCommand.java | 82 + .../cloud/agent/api/storage/SsCommand.java | 51 + .../agent/api/storage/StorageCommand.java | 29 + .../agent/api/storage/UpgradeDiskAnswer.java | 34 + .../agent/api/storage/UpgradeDiskCommand.java | 55 + .../cloud/agent/api/storage/UploadAnswer.java | 128 + .../agent/api/storage/UploadCommand.java | 146 + .../api/storage/UploadProgressCommand.java | 54 + .../resource/virtualnetwork/ConfigItem.java | 35 + .../virtualnetwork/FileConfigItem.java | 85 + .../virtualnetwork/ScriptConfigItem.java | 69 + .../resource/virtualnetwork/VRScripts.java | 69 + .../virtualnetwork/VirtualRouterDeployer.java | 32 + .../VirtualRoutingResource.java | 398 + .../facade/AbstractConfigItemFacade.java | 138 + .../facade/BumpUpPriorityConfigItem.java | 39 + .../facade/CreateIpAliasConfigItem.java | 57 + .../facade/DeleteIpAliasConfigItem.java | 64 + .../facade/DhcpEntryConfigItem.java | 49 + .../facade/DnsMasqConfigItem.java | 56 + .../facade/IpAssociationConfigItem.java | 59 + .../facade/LoadBalancerConfigItem.java | 74 + .../facade/RemoteAccessVpnConfigItem.java | 48 + .../facade/SavePasswordConfigItem.java | 47 + .../facade/SetFirewallRulesConfigItem.java | 58 + .../facade/SetGuestNetworkConfigItem.java | 68 + .../facade/SetMonitorServiceConfigItem.java | 47 + .../facade/SetNetworkAclConfigItem.java | 108 + .../SetPortForwardingRulesConfigItem.java | 59 + .../SetPortForwardingRulesVpcConfigItem.java | 33 + .../facade/SetSourceNatConfigItem.java | 54 + .../facade/SetStaticNatRulesConfigItem.java | 56 + .../facade/SetStaticRouteConfigItem.java | 57 + .../facade/Site2SiteVpnConfigItem.java | 49 + .../facade/VmDataConfigItem.java | 48 + .../facade/VpnUsersConfigItem.java | 54 + .../virtualnetwork/model/AclRule.java | 60 + .../virtualnetwork/model/AllAclRule.java | 33 + .../virtualnetwork/model/ConfigBase.java | 60 + .../virtualnetwork/model/DhcpConfig.java | 45 + .../virtualnetwork/model/DhcpConfigEntry.java | 72 + .../virtualnetwork/model/FirewallRule.java | 175 + .../virtualnetwork/model/FirewallRules.java | 42 + .../virtualnetwork/model/ForwardingRule.java | 91 + .../virtualnetwork/model/ForwardingRules.java | 42 + .../virtualnetwork/model/GuestNetwork.java | 122 + .../virtualnetwork/model/IcmpAclRule.java | 53 + .../virtualnetwork/model/IpAddress.java | 134 + .../virtualnetwork/model/IpAddressAlias.java | 72 + .../virtualnetwork/model/IpAliases.java | 44 + .../virtualnetwork/model/IpAssociation.java | 42 + .../model/LoadBalancerRule.java | 104 + .../model/LoadBalancerRules.java | 43 + .../virtualnetwork/model/MonitorService.java | 52 + .../virtualnetwork/model/NetworkACL.java | 102 + .../virtualnetwork/model/ProtocolAclRule.java | 43 + .../virtualnetwork/model/RemoteAccessVpn.java | 98 + .../virtualnetwork/model/Site2SiteVpn.java | 164 + .../virtualnetwork/model/StaticNatRule.java | 82 + .../virtualnetwork/model/StaticNatRules.java | 44 + .../virtualnetwork/model/StaticRoute.java | 62 + .../virtualnetwork/model/StaticRoutes.java | 44 + .../virtualnetwork/model/TcpAclRule.java | 53 + .../virtualnetwork/model/UdpAclRule.java | 53 + .../resource/virtualnetwork/model/VmData.java | 54 + .../virtualnetwork/model/VmDhcpConfig.java | 123 + .../virtualnetwork/model/VmPassword.java | 52 + .../virtualnetwork/model/VpnUser.java | 62 + .../virtualnetwork/model/VpnUserList.java | 44 + .../agent/transport/ArrayTypeAdaptor.java | 86 + .../agent/transport/InterfaceTypeAdaptor.java | 67 + .../transport/LoggingExclusionStrategy.java | 58 + .../com/cloud/agent/transport/Request.java | 658 + .../com/cloud/agent/transport/Response.java | 74 + .../UnsupportedVersionException.java | 43 + .../cloud/exception/UsageServerException.java | 37 + .../main/java/com/cloud/host/HostInfo.java | 29 + .../info/ConsoleProxyConnectionInfo.java | 32 + .../java/com/cloud/info/ConsoleProxyInfo.java | 98 + .../com/cloud/info/ConsoleProxyStatus.java | 31 + .../cloud/info/RunningHostInfoAgregator.java | 88 + .../com/cloud/info/SecStorageVmLoadInfo.java | 51 + .../cloud/network/HAProxyConfigurator.java | 688 + .../network/LoadBalancerConfigurator.java | 37 + .../cloud/network/LoadBalancerValidator.java | 31 + .../CreateLoadBalancerApplianceAnswer.java | 75 + .../DestroyLoadBalancerApplianceAnswer.java | 30 + .../resource/TrafficSentinelResource.java | 345 + .../com/cloud/resource/CommandWrapper.java | 33 + .../com/cloud/resource/RequestWrapper.java | 146 + .../com/cloud/resource/ResourceListener.java | 109 + .../com/cloud/resource/ResourceWrapper.java | 35 + .../com/cloud/resource/ServerResource.java | 73 + .../cloud/resource/ServerResourceBase.java | 310 + .../hypervisor/HypervisorResource.java | 56 + .../java/com/cloud/serializer/GsonHelper.java | 90 + .../cloud/serializer/SerializerHelper.java | 193 + .../com/cloud/storage/JavaStorageLayer.java | 315 + .../java/com/cloud/storage/StorageLayer.java | 155 + .../storage/resource/StoragePoolResource.java | 37 + .../storage/resource/StorageProcessor.java | 71 + .../StorageSubsystemCommandHandler.java | 28 + .../StorageSubsystemCommandHandlerBase.java | 155 + .../storage/template/FtpTemplateUploader.java | 229 + .../template/HttpTemplateDownloader.java | 439 + .../cloud/storage/template/IsoProcessor.java | 75 + .../template/LocalTemplateDownloader.java | 184 + .../com/cloud/storage/template/Processor.java | 57 + .../storage/template/QCOW2Processor.java | 110 + .../storage/template/RawImageProcessor.java | 75 + .../template/S3TemplateDownloader.java | 379 + .../template/ScpTemplateDownloader.java | 151 + .../cloud/storage/template/TARProcessor.java | 80 + .../storage/template/TemplateConstants.java | 38 + .../storage/template/TemplateDownloader.java | 95 + .../template/TemplateDownloaderBase.java | 152 + .../storage/template/TemplateLocation.java | 218 + .../storage/template/TemplateUploader.java | 89 + .../cloud/storage/template/VhdProcessor.java | 132 + .../storage/command/AttachAnswer.java | 48 + .../storage/command/AttachCommand.java | 76 + .../command/AttachPrimaryDataStoreAnswer.java | 57 + .../command/AttachPrimaryDataStoreCmd.java | 44 + .../storage/command/CopyCmdAnswer.java | 40 + .../storage/command/CopyCommand.java | 96 + .../storage/command/CreateObjectAnswer.java | 44 + .../storage/command/CreateObjectCommand.java | 49 + .../command/CreatePrimaryDataStoreCmd.java | 44 + .../storage/command/DeleteCommand.java | 49 + .../storage/command/DettachAnswer.java | 48 + .../storage/command/DettachCommand.java | 95 + .../storage/command/DownloadCommand.java | 184 + .../command/DownloadProgressCommand.java | 52 + .../storage/command/ForgetObjectCmd.java | 44 + .../command/IntroduceObjectAnswer.java | 35 + .../storage/command/IntroduceObjectCmd.java | 44 + .../command/RevertSnapshotCommand.java | 49 + .../command/SnapshotAndCopyAnswer.java | 41 + .../command/SnapshotAndCopyCommand.java | 58 + .../command/StorageSubSystemCommand.java | 26 + .../TemplateOrVolumePostUploadCommand.java | 199 + .../storage/command/UploadStatusAnswer.java | 88 + .../storage/command/UploadStatusCommand.java | 53 + .../cloudstack/storage/to/ImageStoreTO.java | 110 + .../storage/to/PrimaryDataStoreTO.java | 146 + .../storage/to/SnapshotObjectTO.java | 175 + .../storage/to/TemplateObjectTO.java | 222 + .../cloudstack/storage/to/VolumeObjectTO.java | 255 + .../cloudstack/allocator/module.properties | 21 + .../spring-core-allocator-context.xml | 34 + ...ifecycle-allocator-context-inheritable.xml | 44 + .../META-INF/cloudstack/api/module.properties | 21 + ...core-lifecycle-api-context-inheritable.xml | 66 + .../cloudstack/backend/module.properties | 21 + .../cloudstack/bootstrap/module.properties | 20 + .../spring-bootstrap-context-inheritable.xml | 41 + .../bootstrap/spring-bootstrap-context.xml | 35 + .../cloudstack/compute/module.properties | 21 + ...-lifecycle-compute-context-inheritable.xml | 47 + .../cloudstack/core/module.properties | 21 + .../cloudstack/core/spring-core-context.xml | 38 + ...ore-lifecycle-core-context-inheritable.xml | 43 + .../spring-core-registry-core-context.xml | 304 + .../cloudstack/discoverer/module.properties | 21 + ...fecycle-discoverer-context-inheritable.xml | 37 + .../cloudstack/network/module.properties | 21 + ...-lifecycle-network-context-inheritable.xml | 106 + .../cloudstack/planner/module.properties | 21 + ...-lifecycle-planner-context-inheritable.xml | 48 + .../cloudstack/storage/module.properties | 21 + ...-lifecycle-storage-context-inheritable.xml | 82 + .../cloudstack/system/module.properties | 21 + ...spring-core-system-context-inheritable.xml | 56 + .../system/spring-core-system-context.xml | 53 + .../api/routing/SetNetworkACLCommandTest.java | 53 + .../virtualnetwork/ConfigHelperTest.java | 285 + .../VirtualRoutingResourceTest.java | 919 + .../cloud/agent/transport/RequestTest.java | 252 + .../network/HAProxyConfiguratorTest.java | 121 + .../template/LocalTemplateDownloaderTest.java | 43 + .../storage/template/QCOW2ProcessorTest.java | 110 + .../storage/template/VhdProcessorTest.java | 111 + .../agent/test/AgentControlAnswerTest.java | 38 + .../agent/test/AgentControlCommandTest.java | 36 + .../cloudstack/api/agent/test/AnswerTest.java | 75 + .../api/agent/test/AttachIsoCommandTest.java | 83 + .../agent/test/BackupSnapshotAnswerTest.java | 77 + .../agent/test/BackupSnapshotCommandTest.java | 268 + .../agent/test/BumpUpPriorityCommandTest.java | 80 + .../api/agent/test/CancelCommandTest.java | 49 + .../api/agent/test/ChangeAgentAnswerTest.java | 46 + .../agent/test/ChangeAgentCommandTest.java | 52 + .../api/agent/test/CheckHealthAnswerTest.java | 52 + .../agent/test/CheckHealthCommandTest.java | 43 + .../agent/test/CheckNetworkAnswerTest.java | 279 + .../agent/test/CheckNetworkCommandTest.java | 55 + .../agent/test/CheckOnHostCommandTest.java | 531 + .../api/agent/test/SnapshotCommandTest.java | 230 + .../plugins/acl/static-role-based/pom.xml | 11 + .../acl/StaticRoleBasedAPIAccessChecker.java | 141 + .../acl-static-role-based/module.properties | 18 + .../spring-acl-static-role-based-context.xml | 39 + .../explicit-dedication/pom.xml | 14 + .../affinity/ExplicitDedicationProcessor.java | 442 + .../explicit-dedication/module.properties | 18 + .../spring-explicit-dedication-context.xml | 36 + .../host-anti-affinity/pom.xml | 14 + .../affinity/HostAntiAffinityProcessor.java | 144 + .../host-anti-affinity/module.properties | 18 + .../spring-host-anti-affinity-context.xml | 37 + cosmic-core/plugins/api/discovery/pom.xml | 37 + .../command/user/discovery/ListApisCmd.java | 76 + .../api/response/ApiDiscoveryResponse.java | 123 + .../api/response/ApiParameterResponse.java | 89 + .../api/response/ApiResponseResponse.java | 63 + .../discovery/ApiDiscoveryService.java | 27 + .../discovery/ApiDiscoveryServiceImpl.java | 274 + .../discovery/ApiDiscoveryTest.java | 105 + cosmic-core/plugins/api/rate-limit/pom.xml | 26 + .../admin/ratelimit/ResetApiLimitCmd.java | 106 + .../user/ratelimit/GetApiLimitCmd.java | 79 + .../api/response/ApiLimitResponse.java | 86 + .../ratelimit/ApiRateLimitService.java | 39 + .../ratelimit/ApiRateLimitServiceImpl.java | 202 + .../ratelimit/EhcacheLimitStore.java | 91 + .../cloudstack/ratelimit/LimitStore.java | 48 + .../cloudstack/ratelimit/StoreEntry.java | 32 + .../cloudstack/ratelimit/StoreEntryImpl.java | 59 + .../cloudstack/rate-limit/module.properties | 18 + .../rate-limit/spring-rate-limit-context.xml | 32 + .../ratelimit/ApiRateLimitTest.java | 252 + .../ratelimit/integration/APITest.java | 203 + .../ratelimit/integration/LoginResponse.java | 138 + .../integration/RateLimitIntegrationTest.java | 207 + .../plugins/dedicated-resources/pom.xml | 11 + .../api/commands/DedicateClusterCmd.java | 121 + .../api/commands/DedicateHostCmd.java | 124 + .../api/commands/DedicatePodCmd.java | 125 + .../api/commands/DedicateZoneCmd.java | 125 + .../commands/ListDedicatedClustersCmd.java | 119 + .../api/commands/ListDedicatedHostsCmd.java | 116 + .../api/commands/ListDedicatedPodsCmd.java | 116 + .../api/commands/ListDedicatedZonesCmd.java | 116 + .../commands/ReleaseDedicatedClusterCmd.java | 94 + .../api/commands/ReleaseDedicatedHostCmd.java | 94 + .../api/commands/ReleaseDedicatedPodCmd.java | 94 + .../api/commands/ReleaseDedicatedZoneCmd.java | 94 + .../api/response/DedicateClusterResponse.java | 96 + .../api/response/DedicateHostResponse.java | 96 + .../api/response/DedicatePodResponse.java | 99 + .../api/response/DedicateZoneResponse.java | 99 + .../DedicatedResourceManagerImpl.java | 957 + .../dedicated/DedicatedService.java | 63 + ...pring-dedicated-resources-core-context.xml | 33 + .../manager/DedicatedApiUnitTest.java | 335 + .../src/test/resource/dedicatedContext.xml | 45 + .../implicit-dedication/pom.xml | 11 + .../deploy/ImplicitDedicationPlanner.java | 318 + .../implicit-dedication/module.properties | 18 + .../spring-implicit-dedication-context.xml | 21 + .../implicitplanner/ImplicitPlannerTest.java | 595 + .../user-concentrated-pod/pom.xml | 11 + .../deploy/UserConcentratedPodPlanner.java | 141 + .../user-concentrated-pod/module.properties | 18 + .../spring-user-concentrated-pod-context.xml | 35 + .../user-dispersing/pom.xml | 11 + .../cloud/deploy/UserDispersingPlanner.java | 200 + .../ha-planners/skip-heurestics/pom.xml | 11 + .../cloud/deploy/SkipHeuresticsPlanner.java | 61 + .../skip-heurestics/module.properties | 18 + .../spring-skip-heurestics-context.xml | 26 + .../plugins/host-allocators/random/pom.xml | 11 + .../allocator/impl/RandomAllocator.java | 171 + .../host-allocator-random/module.properties | 18 + .../spring-host-allocator-random-context.xml | 34 + .../database/BaremetalDhcpDaoImpl.java | 33 + .../database/BaremetalPxeDaoImpl.java | 29 + .../baremetal/database/BaremetalRctVO.java | 83 + .../manager/BareMetalDiscoverer.java | 282 + .../baremetal/manager/BareMetalGuru.java | 92 + .../baremetal/manager/BareMetalPlanner.java | 173 + .../manager/BareMetalTemplateAdapter.java | 205 + .../baremetal/manager/BaremetalManager.java | 32 + .../manager/BaremetalManagerImpl.java | 156 + .../manager/BaremetalVlanManager.java | 44 + .../manager/BaremetalVlanManagerImpl.java | 271 + .../BareMetalPingServiceImpl.java | 281 + .../networkservice/BareMetalResourceBase.java | 651 + .../networkservice/BaremetaNetworkGuru.java | 174 + .../networkservice/BaremetalDhcpElement.java | 185 + .../networkservice/BaremetalDhcpManager.java | 58 + .../BaremetalDhcpManagerImpl.java | 326 + .../BaremetalDhcpResourceBase.java | 159 + .../networkservice/BaremetalDhcpResponse.java | 90 + .../BaremetalDhcpdResource.java | 141 + .../BaremetalDnsmasqResource.java | 131 + .../BaremetalKickStartPxeResource.java | 202 + .../BaremetalKickStartServiceImpl.java | 399 + .../BaremetalPingPxeResource.java | 259 + .../networkservice/BaremetalPxeElement.java | 204 + .../BaremetalPxeKickStartResponse.java | 41 + .../networkservice/BaremetalPxeManager.java | 66 + .../BaremetalPxeManagerImpl.java | 256 + .../BaremetalPxePingResponse.java | 65 + .../BaremetalPxeResourceBase.java | 159 + .../networkservice/BaremetalPxeResponse.java | 75 + .../networkservice/BaremetalPxeService.java | 65 + .../networkservice/BaremetalRctResponse.java | 56 + .../Force10BaremetalSwitchBackend.java | 254 + .../SecurityGroupHttpClient.java | 208 + .../schema/SecurityGroupRule.java | 156 + .../schema/SecurityGroupVmRuleSet.java | 264 + .../cloudstack/api/AddBaremetalDhcpCmd.java | 142 + .../cloudstack/api/AddBaremetalHostCmd.java | 49 + .../cloudstack/api/AddBaremetalPxeCmd.java | 151 + .../cloudstack/api/AddBaremetalRctCmd.java | 88 + ...BaremetalProvisionDoneNotificationCmd.java | 89 + .../cloudstack/api/DeleteBaremetalRctCmd.java | 87 + .../cloudstack/api/ListBaremetalDhcpCmd.java | 108 + .../api/ListBaremetalPxeServersCmd.java | 92 + .../cloudstack/api/ListBaremetalRctCmd.java | 72 + .../internal-loadbalancer/pom.xml | 14 + .../element/InternalLoadBalancerElement.java | 513 + .../lb/InternalLoadBalancerVMManager.java | 82 + .../lb/InternalLoadBalancerVMManagerImpl.java | 915 + .../core/spring-internallb-core-context.xml | 37 + .../ElementChildTestConfiguration.java | 125 + .../InternalLbElementServiceTest.java | 185 + .../InternalLbElementTest.java | 223 + .../InternalLBVMManagerTest.java | 387 + .../InternalLBVMServiceTest.java | 263 + .../LbChildTestConfiguration.java | 176 + .../src/test/resources/lb_element.xml | 46 + .../src/test/resources/lb_mgr.xml | 50 + .../src/test/resources/lb_svc.xml | 50 + .../network-elements/nicira-nvp/pom.xml | 53 + ...tForwardingRulesOnLogicalRouterAnswer.java | 35 + ...ForwardingRulesOnLogicalRouterCommand.java | 63 + ...nfigurePublicIpsOnLogicalRouterAnswer.java | 32 + ...figurePublicIpsOnLogicalRouterCommand.java | 66 + ...reStaticNatRulesOnLogicalRouterAnswer.java | 44 + ...eStaticNatRulesOnLogicalRouterCommand.java | 65 + .../agent/api/CreateLogicalRouterAnswer.java | 42 + .../agent/api/CreateLogicalRouterCommand.java | 116 + .../agent/api/CreateLogicalSwitchAnswer.java | 38 + .../agent/api/CreateLogicalSwitchCommand.java | 57 + .../api/CreateLogicalSwitchPortAnswer.java | 38 + .../api/CreateLogicalSwitchPortCommand.java | 56 + .../agent/api/DeleteLogicalRouterAnswer.java | 34 + .../agent/api/DeleteLogicalRouterCommand.java | 44 + .../agent/api/DeleteLogicalSwitchAnswer.java | 32 + .../agent/api/DeleteLogicalSwitchCommand.java | 38 + .../api/DeleteLogicalSwitchPortAnswer.java | 32 + .../api/DeleteLogicalSwitchPortCommand.java | 44 + .../agent/api/FindLogicalSwitchAnswer.java | 18 + .../agent/api/FindLogicalSwitchCommand.java | 19 + .../api/FindLogicalSwitchPortAnswer.java | 38 + .../api/FindLogicalSwitchPortCommand.java | 44 + .../agent/api/StartupNiciraNvpCommand.java | 30 + .../api/UpdateLogicalSwitchPortAnswer.java | 38 + .../api/UpdateLogicalSwitchPortCommand.java | 63 + .../api/commands/AddNiciraNvpDeviceCmd.java | 154 + .../commands/DeleteNiciraNvpDeviceCmd.java | 112 + .../ListNiciraNvpDeviceNetworksCmd.java | 111 + .../api/commands/ListNiciraNvpDevicesCmd.java | 108 + .../api/response/NiciraNvpDeviceResponse.java | 88 + .../com/cloud/network/NiciraNvpDeviceVO.java | 99 + .../cloud/network/NiciraNvpNicMappingVO.java | 87 + .../network/NiciraNvpRouterMappingVO.java | 85 + .../com/cloud/network/dao/NiciraNvpDao.java | 35 + .../cloud/network/dao/NiciraNvpDaoImpl.java | 50 + .../network/dao/NiciraNvpNicMappingDao.java | 32 + .../dao/NiciraNvpNicMappingDaoImpl.java | 48 + .../dao/NiciraNvpRouterMappingDao.java | 28 + .../dao/NiciraNvpRouterMappingDaoImpl.java | 48 + .../network/element/NiciraNvpElement.java | 847 + .../element/NiciraNvpElementService.java | 45 + .../guru/NiciraNvpGuestNetworkGuru.java | 277 + .../network/nicira/AccessConfiguration.java | 44 + .../com/cloud/network/nicira/AccessRule.java | 58 + .../java/com/cloud/network/nicira/Acl.java | 23 + .../com/cloud/network/nicira/AclRule.java | 209 + .../com/cloud/network/nicira/Attachment.java | 24 + .../network/nicira/BaseNiciraEntity.java | 85 + .../network/nicira/BaseNiciraNamedEntity.java | 44 + .../network/nicira/ControlClusterStatus.java | 105 + .../network/nicira/DestinationNatRule.java | 113 + .../network/nicira/ExecutionCounter.java | 54 + .../network/nicira/L3GatewayAttachment.java | 55 + .../cloud/network/nicira/LogicalRouter.java | 71 + .../network/nicira/LogicalRouterPort.java | 62 + .../cloud/network/nicira/LogicalSwitch.java | 60 + .../network/nicira/LogicalSwitchPort.java | 82 + .../java/com/cloud/network/nicira/Match.java | 146 + .../com/cloud/network/nicira/NatRule.java | 130 + .../cloud/network/nicira/NatRuleAdapter.java | 49 + .../cloud/network/nicira/NiciraConstants.java | 42 + .../cloud/network/nicira/NiciraNvpApi.java | 627 + .../network/nicira/NiciraNvpApiException.java | 39 + .../cloud/network/nicira/NiciraNvpList.java | 48 + .../cloud/network/nicira/NiciraNvpTag.java | 65 + .../network/nicira/NiciraRestClient.java | 205 + .../cloud/network/nicira/PatchAttachment.java | 41 + .../cloud/network/nicira/RouterNextHop.java | 40 + .../cloud/network/nicira/RoutingConfig.java | 24 + .../network/nicira/RoutingConfigAdapter.java | 52 + .../nicira/RoutingTableRoutingConfig.java | 30 + .../cloud/network/nicira/SecurityProfile.java | 24 + .../cloud/network/nicira/SecurityRule.java | 138 + ...ngleDefaultRouteImplicitRoutingConfig.java | 40 + .../cloud/network/nicira/SourceNatRule.java | 119 + .../network/nicira/TransportZoneBinding.java | 50 + .../cloud/network/nicira/VifAttachment.java | 78 + .../resource/NiciraNvpRequestWrapper.java | 77 + .../network/resource/NiciraNvpResource.java | 357 + .../network/resource/NiciraNvpUtilities.java | 65 + .../NiciraCheckHealthCommandWrapper.java | 64 + ...gurePortForwardingRulesCommandWrapper.java | 123 + ...raNvpConfigurePublicIpsCommandWrapper.java | 61 + ...ConfigureStaticNatRulesCommandWrapper.java | 115 + ...aNvpCreateLogicalRouterCommandWrapper.java | 153 + ...aNvpCreateLogicalSwitchCommandWrapper.java | 73 + ...CreateLogicalSwitchPortCommandWrapper.java | 71 + ...aNvpDeleteLogicalRouterCommandWrapper.java | 50 + ...aNvpDeleteLogicalSwitchCommandWrapper.java | 49 + ...DeleteLogicalSwitchPortCommandWrapper.java | 50 + ...iraNvpFindLogicalSwitchCommandWrapper.java | 59 + ...vpFindLogicalSwitchPortCommandWrapper.java | 60 + .../NiciraNvpMaintainCommandWrapper.java | 36 + .../wrapper/NiciraNvpReadyCommandWrapper.java | 36 + ...UpdateLogicalSwitchPortCommandWrapper.java | 68 + .../network/utils/CommandRetryUtility.java | 90 + .../META-INF/cloudstack/nvp/module.properties | 21 + .../cloudstack/nvp/spring-nvp-context.xml | 42 + .../network/element/NiciraNvpElementTest.java | 216 + .../guru/NiciraNvpGuestNetworkGuruTest.java | 475 + .../network/nicira/ExecutionCounterTest.java | 102 + .../network/nicira/NatRuleAdapterTest.java | 60 + .../com/cloud/network/nicira/NatRuleTest.java | 55 + .../cloud/network/nicira/NiciraNvpApiIT.java | 319 + .../network/nicira/NiciraNvpApiTest.java | 198 + .../network/nicira/NiciraRestClientTest.java | 214 + .../cloud/network/nicira/NiciraTagTest.java | 58 + .../nicira/RoutingConfigAdapterTest.java | 57 + .../resource/NiciraNvpRequestWrapperTest.java | 250 + .../resource/NiciraNvpResourceTest.java | 829 + .../NiciraCheckHealthCommandWrapperTest.java | 80 + .../src/test/resources/config.properties | 23 + cosmic-core/plugins/pom.xml | 63 + .../plugins/storage-allocators/random/pom.xml | 18 + .../allocator/RandomStoragePoolAllocator.java | 79 + .../plugins/storage/image/default/pom.xml | 51 + .../CloudStackImageStoreDriverImpl.java | 141 + .../CloudStackImageStoreLifeCycleImpl.java | 191 + .../CloudStackImageStoreProviderImpl.java | 104 + .../storage-image-default/module.properties | 18 + .../spring-storage-image-default-context.xml | 33 + cosmic-core/plugins/storage/image/s3/pom.xml | 33 + .../driver/S3ImageStoreDriverImpl.java | 110 + .../lifecycle/S3ImageStoreLifeCycleImpl.java | 142 + .../provider/S3ImageStoreProviderImpl.java | 102 + .../storage-image-s3/module.properties | 18 + .../spring-storage-image-s3-context.xml | 34 + .../plugins/storage/volume/default/pom.xml | 36 + .../CloudStackPrimaryDataStoreDriverImpl.java | 383 + ...oudStackPrimaryDataStoreLifeCycleImpl.java | 540 + ...loudStackPrimaryDataStoreProviderImpl.java | 79 + .../storage-volume-default/module.properties | 18 + .../spring-storage-volume-default-context.xml | 35 + cosmic-core/pom.xml | 318 + .../bindir/cloud-external-ipallocator.py | 159 + .../cloud-grab-dependent-library-versions | 75 + .../rc.d/init.d/cloud-ipallocator.in | 96 + .../rc.d/init.d/cloud-ipallocator.in | 96 + .../SYSCONFDIR/init.d/cloud-ipallocator.in | 116 + .../rc.d/init.d/cloud-ipallocator.in | 96 + .../SYSCONFDIR/init.d/cloud-ipallocator.in | 116 + .../SYSCONFDIR/init.d/cloud-ipallocator.in | 110 + .../incubation/cloud-web-ipallocator.py | 159 + cosmic-core/python/lib/cloud_utils.py | 1205 ++ cosmic-core/python/lib/cloudutils/__init__.py | 16 + .../python/lib/cloudutils/cloudException.py | 45 + .../python/lib/cloudutils/configFileOps.py | 177 + cosmic-core/python/lib/cloudutils/db.py | 82 + .../python/lib/cloudutils/globalEnv.py | 50 + .../python/lib/cloudutils/networkConfig.py | 164 + .../python/lib/cloudutils/serviceConfig.py | 742 + .../lib/cloudutils/serviceConfigServer.py | 150 + cosmic-core/python/lib/cloudutils/syscfg.py | 215 + .../python/lib/cloudutils/utilities.py | 250 + cosmic-core/scripts/common/keys/ssl-keys.py | 58 + cosmic-core/scripts/installer/createtmplt.sh | 277 + cosmic-core/scripts/installer/createvolume.sh | 278 + .../scripts/installer/installcentos.sh | 74 + cosmic-core/scripts/installer/installdomp.sh | 67 + .../scripts/installer/run_installer.sh | 40 + .../scripts/network/domr/router_proxy.sh | 47 + .../scripts/network/exdhcp/dhcpd_edithosts.py | 127 + .../network/exdhcp/dnsmasq_edithosts.sh | 85 + .../scripts/network/exdhcp/prepare_dhcpd.sh | 68 + .../scripts/network/exdhcp/prepare_dnsmasq.sh | 241 + .../network/juniper/access-profile-add.xml | 38 + .../network/juniper/access-profile-getall.xml | 28 + .../network/juniper/access-profile-getone.xml | 29 + .../juniper/address-book-entry-add.xml | 37 + .../juniper/address-book-entry-getall.xml | 33 + .../juniper/address-book-entry-getone.xml | 36 + .../network/juniper/address-pool-add.xml | 44 + .../network/juniper/address-pool-getall.xml | 30 + .../network/juniper/address-pool-getone.xml | 31 + .../network/juniper/application-add.xml | 31 + .../network/juniper/application-getone.xml | 29 + .../network/juniper/close-configuration.xml | 21 + .../scripts/network/juniper/commit.xml | 22 + .../network/juniper/dest-nat-pool-add.xml | 37 + .../network/juniper/dest-nat-pool-getall.xml | 30 + .../network/juniper/dest-nat-pool-getone.xml | 33 + .../network/juniper/dest-nat-rule-add.xml | 56 + .../network/juniper/dest-nat-rule-getall.xml | 33 + .../network/juniper/dest-nat-rule-getone.xml | 38 + .../juniper/dynamic-vpn-client-add.xml | 47 + .../juniper/dynamic-vpn-client-getall.xml | 30 + .../juniper/dynamic-vpn-client-getone.xml | 31 + .../scripts/network/juniper/filter-getone.xml | 29 + .../network/juniper/filter-term-getone.xml | 32 + .../juniper/firewall-filter-bytes-getall.xml | 23 + .../juniper/firewall-filter-term-add.xml | 43 + .../juniper/firewall-filter-term-getone.xml | 32 + .../juniper/guest-vlan-filter-term-add.xml | 36 + .../network/juniper/ike-gateway-add.xml | 39 + .../network/juniper/ike-gateway-getall.xml | 30 + .../network/juniper/ike-gateway-getone.xml | 31 + .../network/juniper/ike-policy-add.xml | 36 + .../network/juniper/ike-policy-getall.xml | 30 + .../network/juniper/ike-policy-getone.xml | 31 + .../scripts/network/juniper/ipsec-vpn-add.xml | 36 + .../network/juniper/ipsec-vpn-getall.xml | 30 + .../network/juniper/ipsec-vpn-getone.xml | 31 + cosmic-core/scripts/network/juniper/login.xml | 31 + .../network/juniper/open-configuration.xml | 23 + .../network/juniper/private-interface-add.xml | 41 + .../juniper/private-interface-getall.xml | 29 + .../juniper/private-interface-getone.xml | 33 + .../private-interface-with-filters-add.xml | 49 + .../scripts/network/juniper/proxy-arp-add.xml | 36 + .../network/juniper/proxy-arp-getall.xml | 31 + .../network/juniper/proxy-arp-getone.xml | 36 + .../juniper/public-ip-filter-term-add.xml | 41 + .../scripts/network/juniper/rollback.xml | 21 + .../network/juniper/security-policy-add.xml | 46 + .../juniper/security-policy-getall.xml | 32 + .../juniper/security-policy-getone.xml | 35 + .../network/juniper/security-policy-group.xml | 32 + .../juniper/security-policy-rename.xml | 35 + .../network/juniper/src-nat-pool-add.xml | 34 + .../network/juniper/src-nat-pool-getone.xml | 33 + .../network/juniper/src-nat-rule-add.xml | 48 + .../network/juniper/src-nat-rule-getall.xml | 31 + .../network/juniper/src-nat-rule-getone.xml | 38 + .../network/juniper/static-nat-rule-add.xml | 49 + .../juniper/static-nat-rule-getall.xml | 32 + .../juniper/static-nat-rule-getone.xml | 38 + .../network/juniper/template-entry.xml | 21 + cosmic-core/scripts/network/juniper/test.xml | 34 + .../network/juniper/zone-interface-add.xml | 35 + .../network/juniper/zone-interface-getone.xml | 35 + .../network/ping/baremetal_user_data.py | 104 + .../ping/prepare_kickstart_kernel_initrd.py | 75 + cosmic-core/scripts/storage/checkchildren.sh | 51 + cosmic-core/scripts/storage/installIso.sh | 130 + .../storage/qcow2/create_private_template.sh | 122 + .../scripts/storage/qcow2/createtmplt.sh | 229 + cosmic-core/scripts/storage/qcow2/createvm.sh | 310 + .../scripts/storage/qcow2/createvolume.sh | 230 + cosmic-core/scripts/storage/qcow2/delvm.sh | 77 + .../scripts/storage/qcow2/get_domr_kernel.sh | 131 + cosmic-core/scripts/storage/qcow2/get_iqn.sh | 34 + .../scripts/storage/qcow2/importmpl.sh | 223 + .../scripts/storage/qcow2/listvmdisk.sh | 86 + .../scripts/storage/qcow2/listvmdisksize.sh | 87 + .../scripts/storage/qcow2/listvmtmplt.sh | 65 + .../scripts/storage/qcow2/listvolume.sh | 65 + .../scripts/storage/qcow2/managesnapshot.sh | 326 + .../scripts/storage/qcow2/managevolume.sh | 180 + .../scripts/storage/qcow2/resizevolume.sh | 268 + .../storage/secondary/cloud-install-sys-tmplt | 292 + .../secondary/cloud-install-sys-tmplt.py | 236 + ...reate_privatetemplate_from_snapshot_xen.sh | 98 + .../scripts/storage/secondary/createtmplt.sh | 226 + .../scripts/storage/secondary/createvolume.sh | 226 + .../scripts/storage/secondary/installIso.sh | 130 + .../scripts/storage/secondary/listvmtmplt.sh | 64 + .../scripts/storage/secondary/listvolume.sh | 64 + cosmic-core/scripts/storage/secondary/swift | 1869 ++ cosmic-core/scripts/util/ipmi.py | 212 + cosmic-core/scripts/util/macgen.py | 30 + cosmic-core/scripts/util/prepare_linmin.sh | 33 + cosmic-core/scripts/util/qemu-ifup | 21 + cosmic-core/scripts/util/qemu-ivs-ifup | 20 + .../scripts/vm/hypervisor/kvm/kvmheartbeat.sh | 166 + .../vm/hypervisor/kvm/patchviasocket.pl | 44 + .../scripts/vm/hypervisor/kvm/setup_agent.sh | 228 + .../scripts/vm/hypervisor/ovm3/cloudstack.py | 588 + .../vm/hypervisor/ovm3/storagehealth.py | 259 + .../vm/hypervisor/update_host_passwd.sh | 28 + cosmic-core/scripts/vm/hypervisor/versions.sh | 44 + .../xenserver/add_to_vcpus_params_live.sh | 33 + .../hypervisor/xenserver/check_heartbeat.sh | 75 + .../hypervisor/xenserver/cloud-clean-vlan.sh | 23 + .../hypervisor/xenserver/cloud-plugin-storage | 301 + .../xenserver/cloud-prepare-upgrade.sh | 101 + .../xenserver/cloud-propagate-vlan.sh | 42 + .../xenserver/cloud-setup-bonding.sh | 110 + .../scripts/vm/hypervisor/xenserver/cloudlog | 37 + .../xenserver/cloudstack_pluginlib.py | 890 + .../xenserver/cloudstack_plugins.conf | 21 + .../copy_vhd_from_secondarystorage.sh | 182 + .../xenserver/copy_vhd_to_secondarystorage.sh | 131 + .../create_privatetemplate_from_snapshot.sh | 138 + .../hypervisor/xenserver/kill_copy_process.sh | 45 + .../vm/hypervisor/xenserver/launch_hb.sh | 51 + .../hypervisor/xenserver/make_migratable.sh | 82 + .../vm/hypervisor/xenserver/mockxcpplugin.py | 66 + .../vm/hypervisor/xenserver/network_info.sh | 58 + .../vm/hypervisor/xenserver/ovs-get-bridge.sh | 27 + .../xenserver/ovs-get-dhcp-iface.sh | 25 + .../scripts/vm/hypervisor/xenserver/ovs-pvlan | 145 + .../vm/hypervisor/xenserver/ovs-vif-flows.py | 145 + .../scripts/vm/hypervisor/xenserver/ovstunnel | 345 + .../vm/hypervisor/xenserver/perfmon.py | 261 + .../vm/hypervisor/xenserver/s3xenserver | 432 + .../xenserver/setup_heartbeat_file.sh | 104 + .../xenserver/setup_heartbeat_sr.sh | 104 + .../vm/hypervisor/xenserver/setup_iscsi.sh | 53 + .../vm/hypervisor/xenserver/setupxenserver.sh | 65 + .../vm/hypervisor/xenserver/storagePlugin | 75 + .../scripts/vm/hypervisor/xenserver/swift | 1871 ++ .../vm/hypervisor/xenserver/swiftxenserver | 101 + .../hypervisor/xenserver/upgrade_snapshot.sh | 133 + .../xenserver/upgrade_vnc_config.sh | 24 + .../scripts/vm/hypervisor/xenserver/vmops | 1479 ++ .../vm/hypervisor/xenserver/vmopsSnapshot | 619 + .../vm/hypervisor/xenserver/vmopspremium | 159 + .../vm/hypervisor/xenserver/xcposs/NFSSR.py | 262 + .../vm/hypervisor/xenserver/xcposs/patch | 61 + .../vm/hypervisor/xenserver/xcposs/vmops | 1455 ++ .../hypervisor/xenserver/xcpserver/NFSSR.py | 262 + .../vm/hypervisor/xenserver/xcpserver/patch | 62 + .../xenserver/xen-ovs-vif-flows.rules | 19 + .../vm/hypervisor/xenserver/xenheartbeat.sh | 112 + .../xenserver56/InterfaceReconfigure.py | 867 + .../hypervisor/xenserver/xenserver56/NFSSR.py | 268 + .../vm/hypervisor/xenserver/xenserver56/patch | 66 + .../xenserver/xenserver56fp1/NFSSR.py | 260 + .../hypervisor/xenserver/xenserver56fp1/patch | 65 + .../hypervisor/xenserver/xenserver60/NFSSR.py | 282 + .../vm/hypervisor/xenserver/xenserver60/patch | 71 + .../vm/hypervisor/xenserver/xenserver62/patch | 67 + .../vm/hypervisor/xenserver/xenserver65/patch | 67 + .../vm/hypervisor/xenserver/xs_cleanup.sh | 69 + .../scripts/vm/network/ovs-pvlan-cleanup.sh | 23 + .../scripts/vm/network/ovs-pvlan-dhcp-host.sh | 123 + .../scripts/vm/network/ovs-pvlan-vm.sh | 100 + .../scripts/vm/network/security_group.py | 1050 ++ .../vm/network/vnet/cloudstack_pluginlib.py | 445 + .../scripts/vm/network/vnet/modifyvlan.sh | 194 + .../scripts/vm/network/vnet/modifyvxlan.sh | 239 + .../scripts/vm/network/vnet/ovstunnel.py | 306 + cosmic-core/scripts/vm/pingtest.sh | 168 + cosmic-core/scripts/vm/systemvm/id_rsa.cloud | 27 + cosmic-core/scripts/vm/systemvm/injectkeys.py | 144 + cosmic-core/scripts/vm/systemvm/injectkeys.sh | 103 + .../server/conf/cloudstack-limits.conf.in | 21 + cosmic-core/server/conf/cloudstack-sudoers.in | 25 + cosmic-core/server/conf/log4j-cloud.xml.in | 133 + .../server/conf/migration-components.xml | 46 + cosmic-core/server/pom.xml | 210 + .../server/scripts/vmops-fix-mysql-config | 29 + .../com/cloud/account/SecurityManager.java | 23 + .../cloud/acl/AffinityGroupAccessChecker.java | 97 + .../java/com/cloud/acl/DomainChecker.java | 365 + .../allocator/impl/FirstFitAllocator.java | 571 + .../impl/FirstFitRoutingAllocator.java | 45 + .../allocator/impl/RecreateHostAllocator.java | 162 + .../allocator/impl/TestingAllocator.java | 86 + .../impl/UserConcentratedAllocator.java | 323 + .../manager/authn/AgentAuthnException.java | 37 + .../agent/manager/authn/AgentAuthorizer.java | 24 + .../authn/impl/BasicAgentAuthManager.java | 69 + .../com/cloud/alert/AlertManagerImpl.java | 864 + .../com/cloud/alert/ClusterAlertAdapter.java | 122 + .../cloud/alert/ConsoleProxyAlertAdapter.java | 164 + .../alert/SecondaryStorageVmAlertAdapter.java | 168 + .../com/cloud/api/ApiAsyncJobDispatcher.java | 138 + .../main/java/com/cloud/api/ApiDBUtils.java | 1929 ++ .../java/com/cloud/api/ApiDispatcher.java | 153 + .../java/com/cloud/api/ApiGsonHelper.java | 37 + .../com/cloud/api/ApiResponseGsonHelper.java | 99 + .../java/com/cloud/api/ApiResponseHelper.java | 3691 ++++ .../com/cloud/api/ApiSerializerHelper.java | 83 + .../main/java/com/cloud/api/ApiServer.java | 1325 ++ .../main/java/com/cloud/api/ApiServlet.java | 363 + .../cloud/api/EncodedStringTypeAdapter.java | 51 + .../cloud/api/ResponseObjectTypeAdapter.java | 93 + .../com/cloud/api/SerializationContext.java | 43 + .../com/cloud/api/StringMapTypeAdapter.java | 44 + .../auth/APIAuthenticationManagerImpl.java | 107 + .../auth/DefaultLoginAPIAuthenticatorCmd.java | 189 + .../DefaultLogoutAPIAuthenticatorCmd.java | 84 + .../api/dispatch/CommandCreationWorker.java | 58 + .../com/cloud/api/dispatch/DispatchChain.java | 41 + .../api/dispatch/DispatchChainFactory.java | 72 + .../com/cloud/api/dispatch/DispatchTask.java | 58 + .../cloud/api/dispatch/DispatchWorker.java | 30 + .../ParamGenericValidationWorker.java | 115 + .../api/dispatch/ParamProcessWorker.java | 457 + .../cloud/api/dispatch/ParamUnpackWorker.java | 115 + .../dispatch/SpecificCmdValidationWorker.java | 34 + .../main/java/com/cloud/api/doc/Alert.java | 42 + .../com/cloud/api/doc/ApiXmlDocReader.java | 292 + .../com/cloud/api/doc/ApiXmlDocWriter.java | 542 + .../main/java/com/cloud/api/doc/Argument.java | 119 + .../main/java/com/cloud/api/doc/Command.java | 117 + .../com/cloud/api/query/QueryManagerImpl.java | 3591 ++++ .../cloud/api/query/ViewResponseHelper.java | 465 + .../cloud/api/query/dao/AccountJoinDao.java | 35 + .../api/query/dao/AccountJoinDaoImpl.java | 233 + .../api/query/dao/AffinityGroupJoinDao.java | 36 + .../query/dao/AffinityGroupJoinDaoImpl.java | 142 + .../cloud/api/query/dao/AsyncJobJoinDao.java | 31 + .../api/query/dao/AsyncJobJoinDaoImpl.java | 100 + .../api/query/dao/DataCenterJoinDao.java | 31 + .../api/query/dao/DataCenterJoinDaoImpl.java | 120 + .../api/query/dao/DiskOfferingJoinDao.java | 30 + .../query/dao/DiskOfferingJoinDaoImpl.java | 92 + .../cloud/api/query/dao/DomainJoinDao.java | 35 + .../api/query/dao/DomainJoinDaoImpl.java | 198 + .../api/query/dao/DomainRouterJoinDao.java | 37 + .../query/dao/DomainRouterJoinDaoImpl.java | 300 + .../com/cloud/api/query/dao/HostJoinDao.java | 44 + .../cloud/api/query/dao/HostJoinDaoImpl.java | 437 + .../com/cloud/api/query/dao/HostTagDao.java | 30 + .../cloud/api/query/dao/HostTagDaoImpl.java | 125 + .../api/query/dao/ImageStoreJoinDao.java | 37 + .../api/query/dao/ImageStoreJoinDaoImpl.java | 161 + .../api/query/dao/InstanceGroupJoinDao.java | 31 + .../query/dao/InstanceGroupJoinDaoImpl.java | 71 + .../api/query/dao/ProjectAccountJoinDao.java | 31 + .../query/dao/ProjectAccountJoinDaoImpl.java | 77 + .../query/dao/ProjectInvitationJoinDao.java | 30 + .../dao/ProjectInvitationJoinDaoImpl.java | 79 + .../cloud/api/query/dao/ProjectJoinDao.java | 37 + .../api/query/dao/ProjectJoinDaoImpl.java | 165 + .../api/query/dao/ResourceTagJoinDao.java | 39 + .../api/query/dao/ResourceTagJoinDaoImpl.java | 170 + .../api/query/dao/SecurityGroupJoinDao.java | 37 + .../query/dao/SecurityGroupJoinDaoImpl.java | 272 + .../api/query/dao/ServiceOfferingJoinDao.java | 30 + .../query/dao/ServiceOfferingJoinDaoImpl.java | 98 + .../api/query/dao/StoragePoolJoinDao.java | 41 + .../api/query/dao/StoragePoolJoinDaoImpl.java | 242 + .../cloud/api/query/dao/StorageTagDao.java | 30 + .../api/query/dao/StorageTagDaoImpl.java | 125 + .../cloud/api/query/dao/TemplateJoinDao.java | 51 + .../api/query/dao/TemplateJoinDaoImpl.java | 433 + .../api/query/dao/UserAccountJoinDao.java | 38 + .../api/query/dao/UserAccountJoinDaoImpl.java | 110 + .../cloud/api/query/dao/UserVmJoinDao.java | 42 + .../api/query/dao/UserVmJoinDaoImpl.java | 472 + .../cloud/api/query/dao/VolumeJoinDao.java | 37 + .../api/query/dao/VolumeJoinDaoImpl.java | 308 + .../com/cloud/api/query/vo/AccountJoinVO.java | 380 + .../api/query/vo/AffinityGroupJoinVO.java | 212 + .../cloud/api/query/vo/AsyncJobJoinVO.java | 222 + .../com/cloud/api/query/vo/BaseViewVO.java | 44 + .../api/query/vo/ControlledViewEntity.java | 46 + .../cloud/api/query/vo/DataCenterJoinVO.java | 224 + .../api/query/vo/DiskOfferingJoinVO.java | 242 + .../com/cloud/api/query/vo/DomainJoinVO.java | 503 + .../api/query/vo/DomainRouterJoinVO.java | 535 + .../com/cloud/api/query/vo/EventJoinVO.java | 232 + .../com/cloud/api/query/vo/HostJoinVO.java | 342 + .../com/cloud/api/query/vo/HostTagVO.java | 61 + .../cloud/api/query/vo/ImageStoreJoinVO.java | 145 + .../api/query/vo/InstanceGroupJoinVO.java | 165 + .../api/query/vo/ProjectAccountJoinVO.java | 130 + .../api/query/vo/ProjectInvitationJoinVO.java | 169 + .../com/cloud/api/query/vo/ProjectJoinVO.java | 214 + .../cloud/api/query/vo/ResourceTagJoinVO.java | 187 + .../api/query/vo/SecurityGroupJoinVO.java | 311 + .../api/query/vo/ServiceOfferingJoinVO.java | 290 + .../cloud/api/query/vo/StoragePoolJoinVO.java | 266 + .../com/cloud/api/query/vo/StorageTagVO.java | 61 + .../cloud/api/query/vo/TemplateJoinVO.java | 813 + .../cloud/api/query/vo/UserAccountJoinVO.java | 254 + .../com/cloud/api/query/vo/UserVmJoinVO.java | 910 + .../com/cloud/api/query/vo/VolumeJoinVO.java | 639 + .../api/response/ApiResponseSerializer.java | 385 + .../response/EmptyFieldExclusionStrategy.java | 40 + .../response/SecurityGroupResultObject.java | 217 + .../SecurityGroupRuleResultObject.java | 123 + .../java/com/cloud/async/AsyncJobResult.java | 107 + .../cloud/capacity/CapacityManagerImpl.java | 1087 ++ .../capacity/ComputeCapacityListener.java | 86 + .../capacity/StorageCapacityListener.java | 99 + .../java/com/cloud/configuration/Config.java | 1913 ++ .../ConfigurationManagerImpl.java | 5205 ++++++ .../com/cloud/configuration/ZoneConfig.java | 85 + .../AgentBasedConsoleProxyManager.java | 212 + ...entBasedStandaloneConsoleProxyManager.java | 88 + .../com/cloud/consoleproxy/AgentHook.java | 37 + .../com/cloud/consoleproxy/AgentHookBase.java | 278 + .../ConsoleProxyAlertEventArgs.java | 70 + .../ConsoleProxyBalanceAllocator.java | 75 + .../consoleproxy/ConsoleProxyListener.java | 91 + .../ConsoleProxyManagementState.java | 21 + .../consoleproxy/ConsoleProxyManager.java | 51 + .../consoleproxy/ConsoleProxyManagerImpl.java | 1731 ++ .../consoleproxy/ConsoleProxyService.java | 25 + .../StaticConsoleProxyManager.java | 109 + .../com/cloud/dc/DedicatedResourceVO.java | 173 + .../cloud/dc/dao/DedicatedResourceDao.java | 57 + .../dc/dao/DedicatedResourceDaoImpl.java | 370 + .../deploy/DeploymentPlanningManagerImpl.java | 1496 ++ .../com/cloud/deploy/FirstFitPlanner.java | 560 + .../deploy/PlannerHostReservationVO.java | 119 + .../deploy/dao/PlannerHostReservationDao.java | 32 + .../dao/PlannerHostReservationDaoImpl.java | 73 + .../cloud/event/ActionEventInterceptor.java | 185 + .../com/cloud/event/ActionEventUtils.java | 308 + .../java/com/cloud/event/AlertGenerator.java | 115 + .../com/cloud/event/dao/EventJoinDao.java | 40 + .../com/cloud/event/dao/EventJoinDaoImpl.java | 111 + .../cloud/ha/AbstractInvestigatorImpl.java | 124 + .../cloud/ha/CheckOnAgentInvestigator.java | 68 + .../src/main/java/com/cloud/ha/HaWorkVO.java | 210 + .../ha/HighAvailabilityManagerExtImpl.java | 103 + .../cloud/ha/HighAvailabilityManagerImpl.java | 984 + .../src/main/java/com/cloud/ha/KVMFencer.java | 124 + .../ha/ManagementIPSystemVMInvestigator.java | 124 + .../java/com/cloud/ha/RecreatableFencer.java | 72 + .../com/cloud/ha/UserVmDomRInvestigator.java | 188 + .../com/cloud/ha/XenServerInvestigator.java | 90 + .../com/cloud/ha/dao/HighAvailabilityDao.java | 86 + .../cloud/ha/dao/HighAvailabilityDaoImpl.java | 246 + .../CloudZonesStartupProcessor.java | 484 + .../cloud/hypervisor/HypervisorGuruBase.java | 199 + .../hypervisor/HypervisorGuruManagerImpl.java | 94 + .../java/com/cloud/hypervisor/KVMGuru.java | 110 + .../kvm/discoverer/KvmDummyResourceBase.java | 119 + .../kvm/discoverer/KvmServerDiscoverer.java | 36 + .../discoverer/LibvirtServerDiscoverer.java | 400 + .../metadata/ResourceMetaDataManager.java | 22 + .../metadata/ResourceMetaDataManagerImpl.java | 277 + .../network/ExternalDeviceUsageManager.java | 32 + .../ExternalDeviceUsageManagerImpl.java | 692 + .../ExternalFirewallDeviceManager.java | 107 + .../ExternalFirewallDeviceManagerImpl.java | 844 + .../network/ExternalIpAddressAllocator.java | 157 + .../ExternalLoadBalancerDeviceManager.java | 103 + ...ExternalLoadBalancerDeviceManagerImpl.java | 1244 ++ .../ExternalNetworkDeviceManagerImpl.java | 161 + .../com/cloud/network/IpAddrAllocator.java | 65 + .../cloud/network/IpAddressManagerImpl.java | 2010 +++ .../com/cloud/network/Ipv6AddressManager.java | 28 + .../cloud/network/Ipv6AddressManagerImpl.java | 153 + .../com/cloud/network/NetworkModelImpl.java | 2305 +++ .../com/cloud/network/NetworkServiceImpl.java | 4077 +++++ .../cloud/network/NetworkUsageManager.java | 28 + .../network/NetworkUsageManagerImpl.java | 549 + .../cloud/network/SshKeysDistriMonitor.java | 113 + .../cloud/network/StorageNetworkManager.java | 35 + .../network/StorageNetworkManagerImpl.java | 375 + .../cloud/network/as/AutoScaleManager.java | 26 + .../network/as/AutoScaleManagerImpl.java | 1531 ++ .../element/CloudZonesNetworkElement.java | 267 + .../network/element/SecurityGroupElement.java | 110 + .../network/element/VirtualRouterElement.java | 1173 ++ .../element/VpcVirtualRouterElement.java | 708 + .../network/firewall/FirewallManagerImpl.java | 1032 ++ .../network/guru/ControlNetworkGuru.java | 196 + .../cloud/network/guru/DirectNetworkGuru.java | 370 + .../guru/DirectPodBasedNetworkGuru.java | 222 + .../guru/ExternalGuestNetworkGuru.java | 331 + .../cloud/network/guru/GuestNetworkGuru.java | 467 + .../network/guru/PodBasedNetworkGuru.java | 174 + .../network/guru/PrivateNetworkGuru.java | 246 + .../cloud/network/guru/PublicNetworkGuru.java | 236 + .../network/guru/StorageNetworkGuru.java | 192 + .../network/lb/LBHealthCheckManager.java | 25 + .../network/lb/LBHealthCheckManagerImpl.java | 111 + .../lb/LoadBalancingRulesManagerImpl.java | 2581 +++ .../network/router/CommandSetupHelper.java | 1061 ++ .../cloud/network/router/NetworkHelper.java | 92 + .../network/router/NetworkHelperImpl.java | 737 + .../network/router/NicProfileHelper.java | 32 + .../network/router/NicProfileHelperImpl.java | 137 + .../network/router/RouterControlHelper.java | 71 + .../VirtualNetworkApplianceManager.java | 94 + .../VirtualNetworkApplianceManagerImpl.java | 2550 +++ .../network/router/VpcNetworkHelperImpl.java | 181 + .../VpcVirtualNetworkApplianceManager.java | 77 + ...VpcVirtualNetworkApplianceManagerImpl.java | 762 + .../cloud/network/rules/AdvancedVpnRules.java | 61 + .../cloud/network/rules/BasicVpnRules.java | 48 + .../cloud/network/rules/DhcpEntryRules.java | 80 + .../cloud/network/rules/DhcpPvlanRules.java | 67 + .../cloud/network/rules/DhcpSubNetRules.java | 175 + .../cloud/network/rules/FirewallRules.java | 94 + .../network/rules/IpAssociationRules.java | 48 + .../network/rules/LoadBalancingRules.java | 79 + .../cloud/network/rules/NetworkAclsRules.java | 54 + .../network/rules/NicPlugInOutRules.java | 219 + .../network/rules/PasswordToRouterRules.java | 66 + .../network/rules/PrivateGatewayRules.java | 153 + .../com/cloud/network/rules/RuleApplier.java | 45 + .../network/rules/RuleApplierWrapper.java | 32 + .../cloud/network/rules/RulesManagerImpl.java | 1597 ++ .../network/rules/SshKeyToRouterRules.java | 89 + .../cloud/network/rules/StaticNatImpl.java | 85 + .../cloud/network/rules/StaticNatRules.java | 46 + .../network/rules/StaticRoutesRules.java | 47 + .../cloud/network/rules/UserdataPwdRules.java | 80 + .../network/rules/UserdataToRouterRules.java | 69 + .../rules/VirtualNetworkApplianceFactory.java | 177 + .../network/rules/VpcIpAssociationRules.java | 93 + .../security/LocalSecurityGroupWorkQueue.java | 210 + .../cloud/network/security/RuleUpdateLog.java | 30 + .../security/SecurityGroupListener.java | 203 + .../security/SecurityGroupManager.java | 56 + .../security/SecurityGroupManagerImpl.java | 1398 ++ .../security/SecurityGroupManagerImpl2.java | 324 + .../security/SecurityGroupManagerMBean.java | 57 + .../security/SecurityGroupWorkQueue.java | 40 + .../security/SecurityGroupWorkTracker.java | 118 + .../security/SecurityManagerMBeanImpl.java | 149 + .../network/vpc/NetworkACLManagerImpl.java | 515 + .../network/vpc/NetworkACLServiceImpl.java | 684 + .../network/vpc/PrivateGatewayProfile.java | 118 + .../cloud/network/vpc/PrivateIpAddress.java | 81 + .../com/cloud/network/vpc/VpcManagerImpl.java | 2508 +++ .../VpcPrivateGatewayTransactionCallable.java | 78 + .../vpn/RemoteAccessVpnManagerImpl.java | 763 + .../network/vpn/Site2SiteVpnManager.java | 36 + .../network/vpn/Site2SiteVpnManagerImpl.java | 858 + .../com/cloud/projects/ProjectManager.java | 40 + .../cloud/projects/ProjectManagerImpl.java | 1054 ++ .../com/cloud/resource/DiscovererBase.java | 198 + .../com/cloud/resource/ResourceChecker.java | 107 + .../cloud/resource/ResourceManagerImpl.java | 2546 +++ .../ResourceLimitManagerImpl.java | 1095 ++ .../com/cloud/server/ConfigurationServer.java | 40 + .../cloud/server/ConfigurationServerImpl.java | 1474 ++ .../main/java/com/cloud/server/Criteria.java | 144 + .../com/cloud/server/LockMasterListener.java | 51 + .../com/cloud/server/ManagementServer.java | 70 + .../cloud/server/ManagementServerImpl.java | 4035 +++++ .../java/com/cloud/server/StatsCollector.java | 1025 ++ .../response/BaremetalTemplateResponse.java | 36 + .../api/response/NwDeviceDhcpResponse.java | 73 + .../response/NwDevicePxeServerResponse.java | 74 + .../server/api/response/PxePingResponse.java | 60 + .../cloud/server/auth/UserAuthenticator.java | 47 + .../cloud/servlet/CloudStartupServlet.java | 63 + .../servlet/ConsoleProxyClientParam.java | 133 + .../ConsoleProxyPasswordBasedEncryptor.java | 162 + .../cloud/servlet/ConsoleProxyServlet.java | 689 + .../cloud/servlet/StaticResourceServlet.java | 115 + .../cloud/storage/CreateSnapshotPayload.java | 59 + .../storage/ImageStoreUploadMonitor.java | 27 + .../storage/ImageStoreUploadMonitorImpl.java | 454 + .../storage/LocalStoragePoolListener.java | 109 + .../java/com/cloud/storage/OCFS2Manager.java | 30 + .../com/cloud/storage/OCFS2ManagerImpl.java | 206 + .../cloud/storage/RegisterVolumePayload.java | 43 + .../cloud/storage/ResizeVolumePayload.java | 36 + .../com/cloud/storage/StorageManager.java | 128 + .../com/cloud/storage/StorageManagerImpl.java | 2191 +++ .../cloud/storage/StoragePoolAutomation.java | 27 + .../storage/StoragePoolAutomationImpl.java | 397 + .../com/cloud/storage/TemplateProfile.java | 317 + .../cloud/storage/VolumeApiServiceImpl.java | 2893 +++ .../download/DownloadAbandonedState.java | 47 + .../storage/download/DownloadActiveState.java | 103 + .../download/DownloadCompleteState.java | 48 + .../storage/download/DownloadErrorState.java | 85 + .../download/DownloadInProgressState.java | 39 + .../download/DownloadInactiveState.java | 51 + .../storage/download/DownloadListener.java | 396 + .../storage/download/DownloadMonitor.java | 35 + .../storage/download/DownloadMonitorImpl.java | 289 + .../cloud/storage/download/DownloadState.java | 87 + .../storage/download/NotDownloadedState.java | 40 + .../listener/SnapshotStateListener.java | 123 + .../storage/listener/StoragePoolMonitor.java | 131 + .../storage/listener/StorageSyncListener.java | 83 + .../storage/listener/VolumeStateListener.java | 138 + .../storage/monitor/StorageHostMonitor.java | 24 + .../DummySecondaryStorageResource.java | 212 + .../secondary/SecStorageVmAlertEventArgs.java | 72 + .../secondary/SecondaryStorageListener.java | 104 + .../SecondaryStorageVmAllocator.java | 27 + .../SecondaryStorageVmDefaultAllocator.java | 40 + .../secondary/SecondaryStorageVmManager.java | 59 + .../storage/snapshot/SnapshotManager.java | 66 + .../storage/snapshot/SnapshotManagerImpl.java | 1176 ++ .../storage/snapshot/SnapshotScheduler.java | 45 + .../snapshot/SnapshotSchedulerImpl.java | 446 + .../storage/upload/NotUploadedState.java | 39 + .../storage/upload/UploadAbandonedState.java | 43 + .../storage/upload/UploadActiveState.java | 104 + .../storage/upload/UploadCompleteState.java | 45 + .../storage/upload/UploadErrorState.java | 83 + .../storage/upload/UploadInProgressState.java | 39 + .../storage/upload/UploadInactiveState.java | 50 + .../cloud/storage/upload/UploadListener.java | 482 + .../cloud/storage/upload/UploadMonitor.java | 56 + .../storage/upload/UploadMonitorImpl.java | 513 + .../com/cloud/storage/upload/UploadState.java | 88 + .../cloud/tags/TaggedResourceManagerImpl.java | 368 + .../template/HypervisorTemplateAdapter.java | 495 + .../com/cloud/template/TemplateAdapter.java | 79 + .../cloud/template/TemplateAdapterBase.java | 458 + .../cloud/template/TemplateManagerImpl.java | 1902 ++ .../java/com/cloud/test/DatabaseConfig.java | 1479 ++ .../java/com/cloud/test/IPRangeConfig.java | 666 + .../java/com/cloud/test/PodZoneConfig.java | 570 + .../com/cloud/usage/UsageServiceImpl.java | 441 + .../java/com/cloud/user/AccountManager.java | 201 + .../com/cloud/user/AccountManagerImpl.java | 2588 +++ .../java/com/cloud/user/DomainManager.java | 53 + .../com/cloud/user/DomainManagerImpl.java | 665 + .../cloud/uuididentity/UUIDManagerImpl.java | 110 + .../com/cloud/vm/SystemVmLoadScanHandler.java | 40 + .../com/cloud/vm/SystemVmLoadScanner.java | 138 + .../main/java/com/cloud/vm/UserVmManager.java | 117 + .../java/com/cloud/vm/UserVmManagerImpl.java | 5372 ++++++ .../com/cloud/vm/UserVmStateListener.java | 168 + .../vm/snapshot/VMSnapshotManagerImpl.java | 1048 ++ .../vm/snapshot/VmWorkCreateVMSnapshot.java | 41 + .../snapshot/VmWorkDeleteAllVMSnapshots.java | 35 + .../vm/snapshot/VmWorkDeleteVMSnapshot.java | 35 + .../vm/snapshot/VmWorkRevertToVMSnapshot.java | 35 + .../affinity/AffinityGroupServiceImpl.java | 481 + .../ApplicationLoadBalancerManagerImpl.java | 561 + .../network/lb/CertServiceImpl.java | 536 + .../topology/AdvancedNetworkTopology.java | 229 + .../topology/AdvancedNetworkVisitor.java | 217 + .../topology/BasicNetworkTopology.java | 446 + .../network/topology/BasicNetworkVisitor.java | 320 + .../network/topology/NetworkTopology.java | 90 + .../topology/NetworkTopologyContext.java | 68 + .../topology/NetworkTopologyVisitor.java | 63 + .../cloudstack/region/RegionAccount.java | 287 + .../cloudstack/region/RegionDomain.java | 61 + .../cloudstack/region/RegionManager.java | 167 + .../cloudstack/region/RegionManagerImpl.java | 267 + .../cloudstack/region/RegionServiceImpl.java | 194 + .../region/RegionServiceProvider.java | 25 + .../apache/cloudstack/region/RegionUser.java | 76 + .../cloudstack/region/RegionsApiUtil.java | 311 + .../GlobalLoadBalancingRulesServiceImpl.java | 730 + .../region/gslb/GslbServiceProvider.java | 36 + .../RouterDeploymentDefinition.java | 480 + .../RouterDeploymentDefinitionBuilder.java | 210 + .../VpcRouterDeploymentDefinition.java | 237 + .../spring-server-core-managers-context.xml | 270 + .../core/spring-server-core-misc-context.xml | 82 + .../module.properties | 18 + ...g-server-alert-adapter-backend-context.xml | 32 + .../module.properties | 18 + ...g-server-alert-adapter-compute-context.xml | 32 + .../module.properties | 18 + ...g-server-alert-adapter-storage-context.xml | 32 + .../server-allocator/module.properties | 18 + .../spring-server-allocator-context.xml | 48 + .../cloudstack/server-api/module.properties | 18 + .../server-api/spring-server-api-context.xml | 33 + .../server-compute/module.properties | 18 + .../spring-server-compute-context.xml | 34 + .../server-discoverer/module.properties | 18 + .../spring-server-discoverer-context.xml | 31 + .../server-fencer/module.properties | 18 + .../spring-server-fencer-context.xml | 37 + .../server-investigator/module.properties | 18 + .../spring-server-investigator-context.xml | 46 + .../server-network/module.properties | 18 + .../spring-server-network-context.xml | 64 + .../server-planner/module.properties | 18 + .../spring-server-planner-context.xml | 34 + .../server-storage/module.properties | 18 + .../spring-server-storage-context.xml | 35 + .../server-template-adapter/module.properties | 18 + ...spring-server-template-adapter-context.xml | 32 + .../system/spring-server-system-context.xml | 36 + .../cloud/upgrade/databaseCreatorContext.xml | 51 + .../server/src/test/async-job-component.xml | 184 + .../cloud/alert/AlertControlsUnitTest.java | 90 + .../com/cloud/alert/MockAlertManagerImpl.java | 93 + .../src/test/java/com/cloud/api/APITest.java | 202 + .../com/cloud/api/ApiResponseHelperTest.java | 68 + .../java/com/cloud/api/ApiServletTest.java | 274 + .../test/java/com/cloud/api/ListPerfTest.java | 161 + .../java/com/cloud/api/LoginResponse.java | 138 + .../dispatch/CommandCreationWorkerTest.java | 56 + .../dispatch/DispatchChainFactoryTest.java | 56 + .../ParamGenericValidationWorkerTest.java | 223 + .../api/dispatch/ParamProcessWorkerTest.java | 111 + .../SpecificCmdValidationWorkerTest.java | 47 + .../dao/SecurityGroupJoinDaoImplTest.java | 205 + .../cloud/capacity/CapacityManagerTest.java | 79 + .../ConfigurationManagerTest.java | 795 + .../configuration/ValidateIpRangeTest.java | 79 + .../consoleproxy/ConsoleProxyManagerTest.java | 250 + .../com/cloud/event/ActionEventUtilsTest.java | 228 + .../cloud/event/EventControlsUnitTest.java | 88 + .../ha/HighAvailabilityManagerImplTest.java | 247 + .../test/java/com/cloud/ha/KVMFencerTest.java | 196 + .../java/com/cloud/keystore/KeystoreTest.java | 166 + .../metadata/ResourceMetaDataManagerTest.java | 96 + .../network/CreatePrivateNetworkTest.java | 175 + .../network/DedicateGuestVlanRangesTest.java | 399 + ...rnalLoadBalancerDeviceManagerImplTest.java | 223 + .../network/MockFirewallManagerImpl.java | 210 + .../cloud/network/MockNetworkModelImpl.java | 899 + .../NetworkManagerTestComponentLibrary.java | 58 + .../com/cloud/network/NetworkModelTest.java | 80 + .../network/UpdatePhysicalNetworkTest.java | 71 + .../com/cloud/network/dao/NetworkDaoTest.java | 61 + .../element/VirtualRouterElementTest.java | 302 + .../element/VpcVirtualRouterElementTest.java | 188 + .../network/firewall/FirewallManagerTest.java | 219 + .../network/lb/AssignLoadBalancerTest.java | 273 + .../network/lb/UpdateLoadBalancerTest.java | 128 + .../network/router/NetworkHelperImplTest.java | 172 + .../router/RouterControlHelperTest.java | 121 + ...irtualNetworkApplianceManagerImplTest.java | 205 + .../SecurityGroupManagerImpl2Test.java | 77 + .../SecurityGroupManagerImplTest.java | 81 + ...SecurityGroupManagerTestConfiguration.java | 133 + .../security/SecurityGroupQueueTest.java | 219 + .../cloud/network/vpc/VpcManagerImplTest.java | 164 + .../MockRemoteAccessVPNServiceProvider.java | 71 + .../network/vpn/RemoteAccessVpnTest.java | 77 + .../projects/MockProjectManagerImpl.java | 206 + .../resource/MockResourceManagerImpl.java | 605 + .../cloud/resource/ResourceCheckerTest.java | 164 + .../ResourceLimitManagerImplTest.java | 104 + .../server/ConfigurationServerImplTest.java | 166 + .../server/ManagementServerImplTest.java | 109 + .../servlet/ConsoleProxyServletTest.java | 35 + .../servlet/StaticResourceServletTest.java | 235 + .../com/cloud/snapshot/SnapshotDaoTest.java | 55 + .../SnapshotDaoTestConfiguration.java | 60 + .../storage/VolumeApiServiceImplTest.java | 363 + .../cloud/storage/dao/StoragePoolDaoTest.java | 42 + .../dao/StoragePoolDaoTestConfiguration.java | 48 + .../storage/snapshot/SnapshotManagerTest.java | 282 + .../template/TemplateManagerImplTest.java | 635 + .../cloud/user/AccountManagerImplTest.java | 356 + .../cloud/user/MockAccountManagerImpl.java | 404 + .../com/cloud/user/MockDomainManagerImpl.java | 158 + .../com/cloud/vm/FirstFitPlannerTest.java | 387 + .../java/com/cloud/vm/UserVmManagerTest.java | 930 + .../vm/dao/UserVmCloneSettingDaoImplTest.java | 65 + ...serVmCloneSettingDaoTestConfiguration.java | 46 + .../com/cloud/vm/dao/UserVmDaoImplTest.java | 69 + .../vm/dao/UserVmDaoTestConfiguration.java | 46 + .../vm/snapshot/VMSnapshotManagerTest.java | 201 + .../vpc/MockConfigurationManagerImpl.java | 532 + .../com/cloud/vpc/MockNetworkManagerImpl.java | 874 + .../com/cloud/vpc/MockNetworkModelImpl.java | 914 + .../vpc/MockResourceLimitManagerImpl.java | 205 + .../vpc/MockSite2SiteVpnManagerImpl.java | 271 + .../vpc/MockSite2SiteVpnServiceProvider.java | 88 + ...MockVpcVirtualNetworkApplianceManager.java | 272 + .../com/cloud/vpc/NetworkACLManagerTest.java | 338 + .../com/cloud/vpc/NetworkACLServiceTest.java | 279 + .../java/com/cloud/vpc/Site2SiteVpnTest.java | 78 + .../java/com/cloud/vpc/VpcApiUnitTest.java | 149 + .../com/cloud/vpc/VpcTestConfiguration.java | 197 + .../vpc/dao/MockConfigurationDaoImpl.java | 121 + .../com/cloud/vpc/dao/MockNetworkDaoImpl.java | 383 + .../vpc/dao/MockNetworkOfferingDaoImpl.java | 155 + .../MockNetworkOfferingServiceMapDaoImpl.java | 37 + .../vpc/dao/MockNetworkServiceMapDaoImpl.java | 101 + .../com/cloud/vpc/dao/MockVpcDaoImpl.java | 138 + .../cloud/vpc/dao/MockVpcOfferingDaoImpl.java | 41 + .../dao/MockVpcOfferingServiceMapDaoImpl.java | 70 + .../vpc/dao/MockVpcVirtualRouterElement.java | 31 + .../affinity/AffinityApiUnitTest.java | 317 + .../AffinityGroupServiceImplTest.java | 357 + .../lb/ApplicationLoadBalancerTest.java | 384 + .../network/lb/CertServiceTest.java | 758 + .../ChildTestConfiguration.java | 350 + .../CreateNetworkOfferingTest.java | 221 + .../privategw/AclOnPrivateGwTest.java | 289 + .../cloudstack/region/RegionManagerTest.java | 55 + ...obalLoadBalancingRulesServiceImplTest.java | 1006 ++ .../service/ServiceOfferingVOTest.java | 50 + .../RouterDeploymentDefinitionTest.java | 851 + .../RouterDeploymentDefinitionTestBase.java | 141 + .../VpcRouterDeploymentDefinitionTest.java | 315 + .../resources/CloneSettingDaoTestContext.xml | 44 + .../SecurityGroupManagerTestContext.xml | 41 + .../test/resources/SnapshotDaoTestContext.xml | 44 + .../resources/StoragePoolDaoTestContext.xml | 44 + .../test/resources/UserVMDaoTestContext.xml | 44 + .../test/resources/VpcApiUnitTestContext.xml | 44 + .../src/test/resources/VpcTestContext.xml | 79 + .../src/test/resources/appLoadBalancer.xml | 43 + .../test/resources/certs/bad_format_cert.crt | 1 + .../test/resources/certs/dsa_self_signed.key | 20 + .../src/test/resources/certs/expired_cert.crt | 20 + .../src/test/resources/certs/non_root.crt | 13 + .../src/test/resources/certs/non_root.csr | 11 + .../src/test/resources/certs/non_root.key | 15 + .../src/test/resources/certs/non_x509_pem.crt | 17 + .../src/test/resources/certs/root_chain.crt | 13 + .../src/test/resources/certs/root_chain.csr | 11 + .../src/test/resources/certs/root_chain.key | 15 + .../test/resources/certs/rsa_ca_signed.crt | 13 + .../test/resources/certs/rsa_ca_signed.csr | 11 + .../test/resources/certs/rsa_ca_signed.key | 15 + .../test/resources/certs/rsa_self_signed.crt | 13 + .../test/resources/certs/rsa_self_signed.csr | 11 + .../test/resources/certs/rsa_self_signed.key | 15 + .../certs/rsa_self_signed_with_pwd.crt | 13 + .../certs/rsa_self_signed_with_pwd.csr | 11 + .../certs/rsa_self_signed_with_pwd.key | 18 + .../server/src/test/resources/cleanup.sql | 18 + .../test/resources/createNetworkOffering.xml | 55 + .../server/src/test/resources/db.properties | 50 + .../server/src/test/resources/fake.sql | 16 + .../test/resources/network-mgr-component.xml | 184 + .../server/src/test/resources/testContext.xml | 87 + .../server/src/test/sync-queue-component.xml | 26 + .../HypervisorTemplateAdapterTest.java | 285 + cosmic-core/services/console-proxy/pom.xml | 19 + .../services/console-proxy/server/pom.xml | 40 + .../consoleproxy/AjaxFIFOImageCache.java | 83 + .../consoleproxy/AuthenticationException.java | 37 + .../com/cloud/consoleproxy/ConsoleProxy.java | 535 + .../consoleproxy/ConsoleProxyAjaxHandler.java | 409 + .../ConsoleProxyAjaxImageHandler.java | 159 + .../ConsoleProxyAuthenticationResult.java | 81 + .../ConsoleProxyBaseServerFactoryImpl.java | 48 + .../consoleproxy/ConsoleProxyClient.java | 81 + .../consoleproxy/ConsoleProxyClientBase.java | 424 + .../ConsoleProxyClientListener.java | 27 + .../consoleproxy/ConsoleProxyClientParam.java | 137 + .../ConsoleProxyClientStatsCollector.java | 88 + .../consoleproxy/ConsoleProxyCmdHandler.java | 70 + .../consoleproxy/ConsoleProxyGCThread.java | 118 + .../ConsoleProxyHttpHandlerHelper.java | 115 + .../ConsoleProxyLoggerFactory.java | 103 + .../consoleproxy/ConsoleProxyMonitor.java | 152 + .../ConsoleProxyPasswordBasedEncryptor.java | 167 + .../ConsoleProxyResourceHandler.java | 180 + .../ConsoleProxySecureServerFactoryImpl.java | 124 + .../ConsoleProxyServerFactory.java | 31 + .../ConsoleProxyThumbnailHandler.java | 213 + .../consoleproxy/ConsoleProxyVncClient.java | 221 + .../cloud/consoleproxy/InputEventType.java | 53 + .../consoleproxy/util/ITileScanListener.java | 26 + .../cloud/consoleproxy/util/ImageHelper.java | 32 + .../com/cloud/consoleproxy/util/Logger.java | 223 + .../consoleproxy/util/LoggerFactory.java | 21 + .../com/cloud/consoleproxy/util/RawHTTP.java | 246 + .../com/cloud/consoleproxy/util/Region.java | 90 + .../consoleproxy/util/RegionClassifier.java | 58 + .../com/cloud/consoleproxy/util/TileInfo.java | 55 + .../cloud/consoleproxy/util/TileTracker.java | 267 + .../consoleproxy/vnc/BufferedImageCanvas.java | 154 + .../consoleproxy/vnc/FrameBufferCanvas.java | 30 + .../vnc/FrameBufferUpdateListener.java | 26 + .../vnc/PaintNotificationListener.java | 27 + .../cloud/consoleproxy/vnc/RfbConstants.java | 82 + .../com/cloud/consoleproxy/vnc/VncClient.java | 458 + .../vnc/VncClientPacketSender.java | 263 + .../vnc/VncScreenDescription.java | 91 + .../vnc/VncServerPacketReceiver.java | 125 + .../vnc/packet/client/ClientPacket.java | 26 + .../FramebufferUpdateRequestPacket.java | 53 + .../packet/client/KeyboardEventPacket.java | 42 + .../vnc/packet/client/MouseEventPacket.java | 43 + .../vnc/packet/client/SetEncodingsPacket.java | 45 + .../packet/client/SetPixelFormatPacket.java | 75 + .../vnc/packet/server/AbstractRect.java | 53 + .../vnc/packet/server/CopyRect.java | 39 + .../server/FrameBufferSizeChangeRequest.java | 39 + .../server/FramebufferUpdatePacket.java | 100 + .../vnc/packet/server/RawRect.java | 79 + .../consoleproxy/vnc/packet/server/Rect.java | 33 + .../vnc/packet/server/ServerCutText.java | 49 + cosmic-core/services/pom.xml | 19 + .../secondary-storage/controller/pom.xml | 32 + .../PremiumSecondaryStorageManagerImpl.java | 185 + .../SecondaryStorageManagerImpl.java | 1481 ++ ...ondary-storage-controller-core-context.xml | 33 + .../SecondaryStorageManagerTest.java | 192 + .../services/secondary-storage/pom.xml | 16 + .../services/secondary-storage/server/pom.xml | 94 + .../resource/HttpUploadServerHandler.java | 296 + .../LocalNfsSecondaryStorageResource.java | 95 + .../LocalSecondaryStorageResource.java | 240 + .../resource/NfsSecondaryStorageResource.java | 2906 +++ .../resource/SecondaryStorageDiscoverer.java | 311 + .../resource/SecondaryStorageResource.java | 28 + .../SecondaryStorageResourceHandler.java | 26 + .../storage/template/DownloadManager.java | 111 + .../storage/template/DownloadManagerImpl.java | 1100 ++ .../storage/template/UploadEntity.java | 201 + .../storage/template/UploadManager.java | 82 + .../storage/template/UploadManagerImpl.java | 550 + .../module.properties | 18 + ...g-secondary-storage-discoverer-context.xml | 36 + .../LocalNfsSecondaryStorageResourceTest.java | 143 + .../NfsSecondaryStorageResourceTest.java | 108 + .../setup/bindir/cloud-migrate-databases.in | 287 + .../setup/bindir/cloud-set-guest-password.in | 107 + .../setup/bindir/cloud-set-guest-sshkey.in | 89 + .../setup/bindir/cloud-setup-databases.in | 604 + .../setup/bindir/cloud-setup-encryption.in | 275 + cosmic-core/setup/bindir/cloud-sysvmadm.in | 463 + cosmic-core/setup/db/221to222upgrade.sh | 50 + cosmic-core/setup/db/22beta4to22GA.sql | 137 + .../setup/db/create-database-premium.sql | 30 + cosmic-core/setup/db/create-database.sql | 66 + .../setup/db/create-schema-premium.sql | 326 + cosmic-core/setup/db/create-schema.sql | 2481 +++ cosmic-core/setup/db/data-20to21.sql | 39 + .../setup/db/data-22beta1to22beta2.sql | 20 + cosmic-core/setup/db/db/data-217to218.sql | 20 + cosmic-core/setup/db/db/schema-20to21.sql | 201 + cosmic-core/setup/db/db/schema-217to218.sql | 33 + .../setup/db/db/schema-21to22-cleanup.sql | 83 + .../setup/db/db/schema-21to22-premium.sql | 81 + cosmic-core/setup/db/db/schema-21to22.sql | 1019 ++ cosmic-core/setup/db/db/schema-2210to2211.sql | 17 + .../setup/db/db/schema-2211to2212-premium.sql | 62 + cosmic-core/setup/db/db/schema-2211to2212.sql | 63 + cosmic-core/setup/db/db/schema-2212to2213.sql | 77 + cosmic-core/setup/db/db/schema-2213to2214.sql | 90 + .../setup/db/db/schema-2214to30-cleanup.sql | 68 + cosmic-core/setup/db/db/schema-2214to30.sql | 768 + .../setup/db/db/schema-221to222-cleanup.sql | 20 + .../setup/db/db/schema-221to222-premium.sql | 26 + cosmic-core/setup/db/db/schema-221to222.sql | 56 + .../setup/db/db/schema-222to224-cleanup.sql | 38 + .../setup/db/db/schema-222to224-premium.sql | 24 + cosmic-core/setup/db/db/schema-222to224.sql | 196 + .../setup/db/db/schema-224to225-cleanup.sql | 20 + cosmic-core/setup/db/db/schema-224to225.sql | 67 + cosmic-core/setup/db/db/schema-225to226.sql | 52 + .../setup/db/db/schema-227to228-premium.sql | 76 + cosmic-core/setup/db/db/schema-227to228.sql | 168 + cosmic-core/setup/db/db/schema-228to229.sql | 96 + cosmic-core/setup/db/db/schema-229to2210.sql | 81 + .../setup/db/db/schema-22beta1to22beta2.sql | 18 + .../setup/db/db/schema-22beta3to22beta4.sql | 128 + .../setup/db/db/schema-301to302-cleanup.sql | 35 + cosmic-core/setup/db/db/schema-301to302.sql | 64 + cosmic-core/setup/db/db/schema-302to303.sql | 196 + .../setup/db/db/schema-302to40-cleanup.sql | 22 + cosmic-core/setup/db/db/schema-302to40.sql | 477 + .../setup/db/db/schema-304to305-cleanup.sql | 22 + cosmic-core/setup/db/db/schema-304to305.sql | 389 + .../setup/db/db/schema-305to306-cleanup.sql | 26 + cosmic-core/setup/db/db/schema-305to306.sql | 93 + cosmic-core/setup/db/db/schema-306to307.sql | 22 + .../setup/db/db/schema-307to410-cleanup.sql | 43 + cosmic-core/setup/db/db/schema-307to410.sql | 1554 ++ cosmic-core/setup/db/db/schema-30to301.sql | 37 + .../setup/db/db/schema-40to410-cleanup.sql | 21 + cosmic-core/setup/db/db/schema-40to410.sql | 1647 ++ .../setup/db/db/schema-410to420-cleanup.sql | 28 + cosmic-core/setup/db/db/schema-410to420.sql | 2362 +++ cosmic-core/setup/db/db/schema-420to421.sql | 242 + .../setup/db/db/schema-421to430-cleanup.sql | 21 + cosmic-core/setup/db/db/schema-421to430.sql | 1105 ++ .../setup/db/db/schema-430to440-cleanup.sql | 22 + cosmic-core/setup/db/db/schema-430to440.sql | 2492 +++ .../setup/db/db/schema-440to441-cleanup.sql | 20 + cosmic-core/setup/db/db/schema-440to441.sql | 183 + cosmic-core/setup/db/db/schema-441to442.sql | 21 + .../setup/db/db/schema-442to450-cleanup.sql | 33 + cosmic-core/setup/db/db/schema-442to450.sql | 1021 ++ cosmic-core/setup/db/db/schema-443to444.sql | 20 + .../setup/db/db/schema-450to451-cleanup.sql | 20 + cosmic-core/setup/db/db/schema-450to451.sql | 27 + .../setup/db/db/schema-451to452-cleanup.sql | 20 + cosmic-core/setup/db/db/schema-451to452.sql | 39 + .../setup/db/db/schema-452to453-cleanup.sql | 20 + cosmic-core/setup/db/db/schema-452to453.sql | 20 + .../setup/db/db/schema-452to460-cleanup.sql | 23 + cosmic-core/setup/db/db/schema-452to460.sql | 422 + .../setup/db/db/schema-460to461-cleanup.sql | 20 + cosmic-core/setup/db/db/schema-460to461.sql | 52 + .../setup/db/db/schema-461to470-cleanup.sql | 20 + cosmic-core/setup/db/db/schema-461to470.sql | 252 + .../setup/db/db/schema-470to471-cleanup.sql | 20 + cosmic-core/setup/db/db/schema-470to471.sql | 21 + .../setup/db/db/schema-471to480-cleanup.sql | 20 + cosmic-core/setup/db/db/schema-471to480.sql | 20 + .../setup/db/db/schema-480to500-cleanup.sql | 16 + cosmic-core/setup/db/db/schema-480to500.sql | 9 + .../setup/db/db/schema-500to501-cleanup.sql | 47 + cosmic-core/setup/db/db/schema-500to501.sql | 4 + .../setup/db/db/schema-501to510-cleanup.sql | 30 + cosmic-core/setup/db/db/schema-501to510.sql | 46 + cosmic-core/setup/db/db/schema-level.sql | 18 + .../setup/db/db/schema-snapshot-217to224.sql | 29 + .../setup/db/db/schema-snapshot-223to224.sql | 29 + cosmic-core/setup/db/deploy-db-clouddev.sh | 43 + cosmic-core/setup/db/deploy-db-dev.sh | 143 + cosmic-core/setup/db/index-20to21.sql | 60 + cosmic-core/setup/db/index-212to213.sql | 20 + cosmic-core/setup/db/postprocess-20to21.sql | 35 + cosmic-core/setup/db/server-setup.sql | 28 + cosmic-core/setup/db/server-setup.xml | 453 + cosmic-core/setup/db/templates.sql | 596 + .../bindir/cloud-setup-console-proxy.in | 220 + cosmic-core/systemvm/certs/localhost.crt | 22 + cosmic-core/systemvm/certs/localhost.key | 27 + cosmic-core/systemvm/certs/realhostip.crt | 31 + cosmic-core/systemvm/certs/realhostip.csr | 15 + cosmic-core/systemvm/certs/realhostip.key | 24 + .../systemvm/certs/realhostip.keystore | Bin 0 -> 8690 bytes .../systemvm/conf.dom0/agent.properties.in | 46 + .../conf.dom0/consoleproxy.properties.in | 23 + .../systemvm/conf.dom0/log4j-cloud.xml.in | 111 + cosmic-core/systemvm/conf/agent.properties | 18 + .../systemvm/conf/agent.properties.ssvm | 21 + .../systemvm/conf/consoleproxy.properties | 23 + .../systemvm/conf/environment.properties | 2 + cosmic-core/systemvm/conf/log4j-cloud.xml | 112 + cosmic-core/systemvm/css/ajaxviewer.css | 144 + cosmic-core/systemvm/css/logger.css | 139 + .../rc.d/init.d/cloud-console-proxy.in | 96 + .../rc.d/init.d/cloud-console-proxy.in | 96 + .../rc.d/init.d/cloud-console-proxy.in | 96 + .../SYSCONFDIR/init.d/cloud-console-proxy.in | 110 + cosmic-core/systemvm/images/back.gif | Bin 0 -> 149 bytes cosmic-core/systemvm/images/bright-green.png | Bin 0 -> 3903 bytes cosmic-core/systemvm/images/cad.gif | Bin 0 -> 918 bytes cosmic-core/systemvm/images/cannotconnect.jpg | Bin 0 -> 1810 bytes cosmic-core/systemvm/images/clr_button.gif | Bin 0 -> 1274 bytes .../systemvm/images/clr_button_hover.gif | Bin 0 -> 437 bytes cosmic-core/systemvm/images/dot.cur | Bin 0 -> 326 bytes cosmic-core/systemvm/images/gray-green.png | Bin 0 -> 3833 bytes cosmic-core/systemvm/images/grid_headerbg.gif | Bin 0 -> 196 bytes cosmic-core/systemvm/images/left.png | Bin 0 -> 3024 bytes .../systemvm/images/minimize_button.gif | Bin 0 -> 634 bytes .../systemvm/images/minimize_button_hover.gif | Bin 0 -> 227 bytes cosmic-core/systemvm/images/notready.jpg | Bin 0 -> 1827 bytes cosmic-core/systemvm/images/play_button.gif | Bin 0 -> 657 bytes .../systemvm/images/play_button_hover.gif | Bin 0 -> 243 bytes cosmic-core/systemvm/images/right.png | Bin 0 -> 3131 bytes cosmic-core/systemvm/images/right2.png | Bin 0 -> 3156 bytes cosmic-core/systemvm/images/shrink_button.gif | Bin 0 -> 655 bytes .../systemvm/images/shrink_button_hover.gif | Bin 0 -> 243 bytes cosmic-core/systemvm/images/stop_button.gif | Bin 0 -> 649 bytes .../systemvm/images/stop_button_hover.gif | Bin 0 -> 231 bytes cosmic-core/systemvm/images/winlog.png | Bin 0 -> 2629 bytes cosmic-core/systemvm/js/ajaxkeys.js | 646 + cosmic-core/systemvm/js/ajaxviewer.js | 1417 ++ cosmic-core/systemvm/js/cloud.logger.js | 338 + cosmic-core/systemvm/js/handler.js | 72 + cosmic-core/systemvm/js/jquery.js | 19 + .../systemvm/libexec/console-proxy-runner.in | 90 + cosmic-core/systemvm/patches/debian/README | 34 + .../systemvm/patches/debian/buildsystemvm.sh | 577 + .../systemvm/patches/debian/config.dat | 398 + .../debian/config/etc/apache2/httpd.conf | 3 + .../debian/config/etc/apache2/ports.conf | 23 + .../etc/apache2/sites-available/default | 41 + .../etc/apache2/sites-available/default-ssl | 175 + .../config/etc/apache2/vhostexample.conf | 239 + .../patches/debian/config/etc/chef/node.json | 5 + .../patches/debian/config/etc/chef/solo.rb | 21 + .../patches/debian/config/etc/cloud-nic.rules | 2 + .../config/etc/cron.daily/cloud-cleanup | 27 + .../patches/debian/config/etc/default/cloud | 19 + .../config/etc/default/cloud-passwd-srvr | 19 + .../debian/config/etc/dnsmasq.conf.tmpl | 535 + .../debian/config/etc/haproxy/haproxy.cfg | 26 + .../patches/debian/config/etc/init.d/cloud | 157 + .../config/etc/init.d/cloud-early-config | 1616 ++ .../config/etc/init.d/cloud-passwd-srvr | 124 + .../patches/debian/config/etc/init.d/postinit | 178 + .../config/etc/iptables/iptables-consoleproxy | 38 + .../config/etc/iptables/iptables-dhcpsrvr | 58 + .../debian/config/etc/iptables/iptables-elbvm | 34 + .../debian/config/etc/iptables/iptables-ilbvm | 33 + .../config/etc/iptables/iptables-router | 58 + .../config/etc/iptables/iptables-secstorage | 36 + .../config/etc/iptables/iptables-vpcrouter | 42 + .../debian/config/etc/iptables/rt_tables_init | 29 + .../patches/debian/config/etc/iptables/rules | 42 + .../patches/debian/config/etc/logrotate.conf | 25 + .../debian/config/etc/logrotate.d/apache2 | 9 + .../debian/config/etc/logrotate.d/cloud | 28 + .../debian/config/etc/logrotate.d/conntrackd | 14 + .../debian/config/etc/logrotate.d/dnsmasq | 14 + .../debian/config/etc/logrotate.d/haproxy | 10 + .../patches/debian/config/etc/logrotate.d/ppp | 10 + .../debian/config/etc/logrotate.d/rsyslog | 39 + .../config/etc/modprobe.d/aesni_intel.conf | 17 + .../debian/config/etc/modprobe.d/pcspkr.conf | 17 + .../debian/config/etc/profile.d/cloud.sh | 22 + .../patches/debian/config/etc/rc.local | 59 + .../patches/debian/config/etc/rsyslog.conf | 106 + .../patches/debian/config/etc/ssh/sshd_config | 130 + .../patches/debian/config/etc/sysctl.conf | 63 + .../rules.d/75-persistent-net-generator.rules | 120 + .../patches/debian/config/etc/vpcdnsmasq.conf | 422 + .../config/opt/cloud/bin/baremetal_snat.sh | 54 + .../config/opt/cloud/bin/bumpup_priority.sh | 19 + .../config/opt/cloud/bin/checkbatchs2svpn.sh | 25 + .../config/opt/cloud/bin/checkrouter.sh | 36 + .../config/opt/cloud/bin/checks2svpn.sh | 46 + .../debian/config/opt/cloud/bin/cloud-nic.sh | 87 + .../debian/config/opt/cloud/bin/configure.py | 987 + .../config/opt/cloud/bin/cs/CsAddress.py | 688 + .../debian/config/opt/cloud/bin/cs/CsApp.py | 105 + .../config/opt/cloud/bin/cs/CsConfig.py | 98 + .../config/opt/cloud/bin/cs/CsDatabag.py | 155 + .../debian/config/opt/cloud/bin/cs/CsDhcp.py | 142 + .../debian/config/opt/cloud/bin/cs/CsFile.py | 169 + .../config/opt/cloud/bin/cs/CsGuestNetwork.py | 75 + .../config/opt/cloud/bin/cs/CsHelper.py | 257 + .../config/opt/cloud/bin/cs/CsLoadBalancer.py | 86 + .../config/opt/cloud/bin/cs/CsMonitor.py | 43 + .../config/opt/cloud/bin/cs/CsNetfilter.py | 307 + .../config/opt/cloud/bin/cs/CsProcess.py | 65 + .../config/opt/cloud/bin/cs/CsRedundant.py | 384 + .../debian/config/opt/cloud/bin/cs/CsRoute.py | 97 + .../debian/config/opt/cloud/bin/cs/CsRule.py | 44 + .../config/opt/cloud/bin/cs/CsStaticRoutes.py | 98 + .../config/opt/cloud/bin/cs/CsVmPassword.py | 45 + .../config/opt/cloud/bin/cs/__init__.py | 16 + .../debian/config/opt/cloud/bin/cs_cmdline.py | 27 + .../debian/config/opt/cloud/bin/cs_dhcp.py | 49 + .../config/opt/cloud/bin/cs_firewallrules.py | 33 + .../opt/cloud/bin/cs_forwardingrules.py | 79 + .../config/opt/cloud/bin/cs_guestnetwork.py | 41 + .../debian/config/opt/cloud/bin/cs_ip.py | 44 + .../config/opt/cloud/bin/cs_iptables_save.py | 227 + .../config/opt/cloud/bin/cs_loadbalancer.py | 27 + .../config/opt/cloud/bin/cs_monitorservice.py | 26 + .../config/opt/cloud/bin/cs_network_acl.py | 24 + .../opt/cloud/bin/cs_remoteaccessvpn.py | 28 + .../config/opt/cloud/bin/cs_site2sitevpn.py | 28 + .../config/opt/cloud/bin/cs_staticroutes.py | 25 + .../debian/config/opt/cloud/bin/cs_vmdata.py | 23 + .../config/opt/cloud/bin/cs_vpnusers.py | 48 + .../debian/config/opt/cloud/bin/dnsmasq.sh | 130 + .../debian/config/opt/cloud/bin/edithosts.sh | 230 + .../opt/cloud/bin/get_template_version.sh | 46 + .../debian/config/opt/cloud/bin/ilb.sh | 215 + .../debian/config/opt/cloud/bin/ipassoc.sh | 462 + .../config/opt/cloud/bin/ipsectunnel.sh | 316 + .../debian/config/opt/cloud/bin/line_edit.py | 199 + .../config/opt/cloud/bin/loadbalancer.sh | 319 + .../debian/config/opt/cloud/bin/master.py | 59 + .../debian/config/opt/cloud/bin/merge.py | 339 + .../config/opt/cloud/bin/monitor_service.sh | 94 + .../debian/config/opt/cloud/bin/netusage.sh | 155 + .../debian/config/opt/cloud/bin/passwd_server | 26 + .../config/opt/cloud/bin/passwd_server_ip | 32 + .../config/opt/cloud/bin/passwd_server_ip.py | 194 + .../config/opt/cloud/bin/patchsystemvm.sh | 257 + .../config/opt/cloud/bin/savepassword.sh | 46 + .../config/opt/cloud/bin/set_redundant.py | 47 + .../debian/config/opt/cloud/bin/test.sh | 25 + .../config/opt/cloud/bin/update_config.py | 158 + .../debian/config/opt/cloud/bin/vmdata.py | 168 + .../debian/config/opt/cloud/bin/vpc_func.sh | 68 + .../config/opt/cloud/bin/vpc_netusage.sh | 158 + .../config/opt/cloud/bin/vpc_passwd_server | 32 + .../debian/config/opt/cloud/bin/vpc_snat.sh | 102 + .../config/opt/cloud/bin/vpc_staticroute.sh | 134 + .../debian/config/opt/cloud/bin/vr_cfg.sh | 107 + .../debian/config/opt/cloud/templates/README | 2 + .../cloud/templates/arping_gateways.sh.templ | 29 + .../opt/cloud/templates/check_bumpup.sh | 19 + .../cloud/templates/check_heartbeat.sh.templ | 63 + .../opt/cloud/templates/checkrouter.sh.templ | 37 + .../opt/cloud/templates/conntrackd.conf.templ | 417 + .../opt/cloud/templates/heartbeat.sh.templ | 20 + .../opt/cloud/templates/keepalived.conf.templ | 51 + .../debian/config/opt/cloud/testdata/README | 1 + .../config/opt/cloud/testdata/acl0001.json | 54 + .../config/opt/cloud/testdata/dhcp0001.json | 9 + .../config/opt/cloud/testdata/gn0001.json | 10 + .../config/opt/cloud/testdata/ips0001.json | 12 + .../config/opt/cloud/testdata/ips0002.json | 12 + .../config/opt/cloud/testdata/ips0003.json | 12 + .../config/opt/cloud/testdata/s2s0001.json | 16 + .../config/opt/cloud/testdata/vmp0001.json | 1 + .../debian/config/root/.ssh/authorized_keys | 1 + .../debian/config/root/clearUsageRules.sh | 39 + .../patches/debian/config/root/func.sh | 143 + .../debian/config/root/monitorServices.py | 387 + .../patches/debian/config/root/reconfigLB.sh | 40 + .../redundant_router/arping_gateways.sh.templ | 29 + .../root/redundant_router/backup.sh.templ | 39 + .../root/redundant_router/check_bumpup.sh | 19 + .../redundant_router/check_heartbeat.sh.templ | 60 + .../redundant_router/checkrouter.sh.templ | 56 + .../redundant_router/conntrackd.conf.templ | 401 + .../root/redundant_router/disable_pubip.sh | 23 + .../redundant_router/enable_pubip.sh.templ | 50 + .../root/redundant_router/fault.sh.templ | 37 + .../root/redundant_router/heartbeat.sh.templ | 20 + .../redundant_router/keepalived.conf.templ | 57 + .../root/redundant_router/master.sh.templ | 60 + .../redundant_router/primary-backup.sh.templ | 126 + .../config/root/redundant_router/services.sh | 68 + .../config/var/www/html/latest/.htaccess | 24 + .../config/var/www/html/userdata/.htaccess | 1 + .../systemvm/patches/debian/convert.sh | 64 + .../systemvm/patches/debian/qemuconvert.sh | 32 + .../systemvm/patches/debian/systemvm.vmx | 37 + .../systemvm/patches/debian/systemvm.xml | 53 + .../systemvm/patches/debian/vhdconvert.sh | 40 + .../patches/debian/vpn/etc/ipsec.conf | 9 + .../patches/debian/vpn/etc/ipsec.d/l2tp.conf | 33 + .../patches/debian/vpn/etc/ipsec.secrets | 2 + .../patches/debian/vpn/etc/ppp/options.xl2tpd | 14 + .../patches/debian/vpn/etc/xl2tpd/xl2tpd.conf | 6 + .../debian/vpn/opt/cloud/bin/vpn_l2tp.sh | 259 + .../systemvm/patches/debian/xe/xe-daemon | 65 + .../patches/debian/xe/xe-linux-distribution | 267 + .../patches/debian/xe/xe-update-guest-attrs | 226 + cosmic-core/systemvm/pom.xml | 197 + cosmic-core/systemvm/scripts/_run.sh | 71 + cosmic-core/systemvm/scripts/config_auth.sh | 69 + cosmic-core/systemvm/scripts/config_ssl.sh | 220 + cosmic-core/systemvm/scripts/consoleproxy.sh | 33 + cosmic-core/systemvm/scripts/ipfirewall.sh | 50 + cosmic-core/systemvm/scripts/run-proxy.sh | 48 + cosmic-core/systemvm/scripts/run.bat | 18 + cosmic-core/systemvm/scripts/run.sh | 61 + cosmic-core/systemvm/scripts/secstorage.sh | 33 + cosmic-core/systemvm/scripts/ssvm-check.sh | 146 + cosmic-core/systemvm/scripts/utils.sh | 38 + cosmic-core/systemvm/systemvm-descriptor.xml | 107 + .../systemvm/test/python/TestCsAddress.py | 42 + cosmic-core/systemvm/test/python/TestCsApp.py | 38 + .../systemvm/test/python/TestCsCmdLine.py | 46 + .../systemvm/test/python/TestCsConfig.py | 33 + .../systemvm/test/python/TestCsDatabag.py | 33 + .../systemvm/test/python/TestCsDhcp.py | 37 + .../systemvm/test/python/TestCsFile.py | 33 + .../test/python/TestCsGuestNetwork.py | 44 + .../systemvm/test/python/TestCsHelper.py | 35 + .../systemvm/test/python/TestCsInterface.py | 38 + .../systemvm/test/python/TestCsNetfilter.py | 33 + .../systemvm/test/python/TestCsProcess.py | 33 + .../systemvm/test/python/TestCsRedundant.py | 40 + .../systemvm/test/python/TestCsRoute.py | 46 + .../systemvm/test/python/TestCsRule.py | 33 + cosmic-core/systemvm/test/python/runtests.sh | 27 + cosmic-core/systemvm/ui/viewer-bad-sid.ftl | 29 + .../systemvm/ui/viewer-connect-failed.ftl | 29 + cosmic-core/systemvm/ui/viewer-update.ftl | 24 + cosmic-core/systemvm/ui/viewer.ftl | 60 + cosmic-core/systemvm/vm-script/vmops | 119 + cosmic-core/test/bindirbak/cloud-run-test.in | 56 + cosmic-core/test/conf/config.xml | 103 + cosmic-core/test/conf/deploy.properties | 31 + cosmic-core/test/conf/deploy.xml | 184 + cosmic-core/test/conf/log4j-stdout.properties | 23 + cosmic-core/test/conf/log4j.properties | 31 + cosmic-core/test/conf/templates.sql | 17 + cosmic-core/test/conf/tool.properties | 23 + cosmic-core/test/integration/__init__.py | 16 + .../component/cpu_limits/__init__.py | 16 + .../integration/component/maint/__init__.py | 21 + .../integration/component/maint/test_bugs.py | 658 + .../maint/test_dedicate_guest_vlan_ranges.py | 1241 ++ .../maint/test_dedicate_public_ip_range.py | 1453 ++ .../test_egress_rules_host_maintenance.py | 289 + .../maint/test_escalation_templates.py | 403 + .../component/maint/test_escalations_hosts.py | 410 + .../maint/test_explicit_dedication.py | 241 + .../component/maint/test_high_availability.py | 1096 ++ .../maint/test_host_high_availability.py | 850 + .../component/maint/test_hypervisor_limit.py | 225 + .../maint/test_multiple_ip_ranges.py | 792 + .../component/maint/test_redundant_router.py | 1513 ++ ...st_redundant_router_deployment_planning.py | 1011 ++ .../test_redundant_router_network_rules.py | 1426 ++ .../maint/test_vpc_host_maintenance.py | 669 + .../maint/test_vpc_on_host_maintenance.py | 217 + .../test_zone_level_local_storage_setting.py | 734 + .../maint/testpath_disable_enable_zone.py | 1685 ++ .../maint/testpath_disablestoragepool.py | 1615 ++ .../maint/testpath_vMotion_vmware.py | 2984 ++++ .../component/test_VirtualRouter_alerts.py | 214 + .../integration/component/test_accounts.py | 1891 ++ .../component/test_add_remove_network.py | 2050 +++ .../component/test_blocker_bugs.py | 1167 ++ .../component/test_browse_templates.py | 1720 ++ .../component/test_browse_volumes.py | 2716 +++ .../test_concurrent_snapshots_limit.py | 293 + .../component/test_cpu_domain_limits.py | 774 + .../integration/component/test_cpu_limits.py | 759 + .../component/test_cpu_project_limits.py | 355 + .../component/test_deploy_vgpu_vm.py | 2286 +++ .../component/test_deploy_vm_userdata_reg.py | 213 + .../test_dynamic_compute_offering.py | 1752 ++ .../component/test_egress_fw_rules.py | 892 + ...test_escalation_listTemplateDomainAdmin.py | 138 + .../component/test_escalations_instances.py | 4400 +++++ .../component/test_escalations_ipaddresses.py | 3421 ++++ .../component/test_escalations_isos.py | 783 + .../component/test_escalations_networks.py | 2723 +++ .../component/test_escalations_routers.py | 196 + .../test_escalations_securitygroups.py | 564 + .../component/test_escalations_snapshots.py | 630 + .../component/test_escalations_templates.py | 927 + .../component/test_escalations_vmware.py | 274 + .../component/test_escalations_volumes.py | 1861 ++ .../test_escalations_vpncustomergateways.py | 383 + .../integration/component/test_haproxy.py | 899 + .../component/test_host_maintenance.py | 290 + .../component/test_interop_xd_ccp.py | 1332 ++ .../component/test_ip_reservation.py | 1230 ++ .../component/test_lb_secondary_ip.py | 1790 ++ .../component/test_memory_limits.py | 778 + .../component/test_mm_domain_limits.py | 790 + .../component/test_mm_project_limits.py | 358 + .../integration/component/test_overcommit.py | 450 + .../component/test_persistent_networks.py | 2655 +++ .../integration/component/test_portable_ip.py | 1374 ++ .../component/test_project_limits.py | 1112 ++ .../component/test_project_usage.py | 1804 ++ .../component/test_ps_domain_limits.py | 759 + .../integration/component/test_ps_limits.py | 588 + .../component/test_ps_resize_volume.py | 386 + .../test_ps_resource_limits_volume.py | 172 + .../component/test_recurring_snapshots.py | 427 + .../component/test_reset_ssh_keypair.py | 1515 ++ .../integration/component/test_routers.py | 1279 ++ .../component/test_security_groups.py | 2178 +++ .../component/test_shared_networks.py | 3757 ++++ .../test_simultaneous_volume_attach.py | 249 + .../integration/component/test_snapshot_gc.py | 296 + .../component/test_snapshot_limits.py | 304 + .../integration/component/test_snapshots.py | 1318 ++ .../component/test_snapshots_improvement.py | 714 + .../component/test_ss_domain_limits.py | 582 + .../integration/component/test_ss_limits.py | 393 + .../component/test_ss_max_limits.py | 286 + .../component/test_ss_project_limits.py | 262 + .../integration/component/test_stopped_vm.py | 1681 ++ .../component/test_storage_motion.py | 308 + .../integration/component/test_templates.py | 593 + .../test/integration/component/test_usage.py | 1680 ++ .../component/test_vm_passwdenabled.py | 309 + .../integration/component/test_vmware_drs.py | 698 + .../integration/component/test_volumes.py | 1423 ++ .../test/integration/component/test_vpc.py | 2492 +++ .../integration/component/test_vpc_network.py | 2754 +++ .../component/test_vpc_network_lbrules.py | 1010 ++ .../component/test_vpc_network_pfrules.py | 897 + .../test_vpc_network_staticnatrule.py | 712 + .../component/test_vpc_offerings.py | 1203 ++ .../integration/component/test_vpc_routers.py | 1363 ++ .../component/test_vpc_vm_life_cycle.py | 3565 ++++ .../component/test_vpc_vms_deployment.py | 2445 +++ .../test/integration/plugins/test_nicira.py | 581 + .../test/integration/smoke/__init__.py | 16 + .../test/integration/smoke/misc/__init__.py | 16 + .../smoke/misc/test_escalations_templates.py | 211 + .../integration/smoke/test_affinity_groups.py | 161 + .../smoke/test_affinity_groups_projects.py | 188 + .../smoke/test_deploy_vgpu_enabled_vm.py | 230 + .../integration/smoke/test_deploy_vm_iso.py | 152 + .../smoke/test_deploy_vm_root_resize.py | 270 + .../smoke/test_deploy_vm_with_userdata.py | 127 + ...ploy_vms_with_varied_deploymentplanners.py | 235 + .../integration/smoke/test_disk_offerings.py | 288 + .../integration/smoke/test_global_settings.py | 77 + .../smoke/test_guest_vlan_range.py | 133 + .../test/integration/smoke/test_hosts.py | 167 + .../integration/smoke/test_internal_lb.py | 832 + .../test/integration/smoke/test_iso.py | 605 + .../integration/smoke/test_loadbalance.py | 530 + .../smoke/test_multipleips_per_nic.py | 154 + .../test/integration/smoke/test_network.py | 1286 ++ .../integration/smoke/test_network_acl.py | 132 + .../test/integration/smoke/test_nic.py | 290 + .../smoke/test_nic_adapter_type.py | 177 + .../smoke/test_non_contigiousvlan.py | 84 + .../smoke/test_over_provisioning.py | 118 + .../integration/smoke/test_password_server.py | 655 + .../smoke/test_portable_publicip.py | 179 + .../smoke/test_portforward_remove.py | 642 + .../integration/smoke/test_primary_storage.py | 241 + .../integration/smoke/test_privategw_acl.py | 753 + .../integration/smoke/test_public_ip_range.py | 149 + .../test/integration/smoke/test_pvlan.py | 81 + .../test/integration/smoke/test_quota.py | 204 + .../test/integration/smoke/test_regions.py | 84 + .../smoke/test_reset_vm_on_reboot.py | 170 + .../integration/smoke/test_resource_detail.py | 118 + .../smoke/test_router_dhcphosts.py | 397 + .../test/integration/smoke/test_routers.py | 798 + .../test_routers_iptables_default_policy.py | 678 + .../smoke/test_routers_network_ops.py | 1045 ++ .../test/integration/smoke/test_scale_vm.py | 205 + .../smoke/test_secondary_storage.py | 226 + .../smoke/test_service_offerings.py | 396 + .../test/integration/smoke/test_snapshots.py | 203 + .../test/integration/smoke/test_ssvm.py | 1184 ++ .../test/integration/smoke/test_templates.py | 788 + .../integration/smoke/test_usage_events.py | 190 + .../integration/smoke/test_vm_life_cycle.py | 738 + .../integration/smoke/test_vm_snapshots.py | 420 + .../test/integration/smoke/test_volumes.py | 778 + .../integration/smoke/test_vpc_redundant.py | 833 + .../integration/smoke/test_vpc_router_nics.py | 537 + .../test/integration/smoke/test_vpc_vpn.py | 845 + .../test/integration/testpaths/__init__.py | 16 + .../testpaths/testpath_attach_disk_zwps.py | 209 + .../testpath_custom_disk_offering.py | 89 + .../testpaths/testpath_multiple_snapshot.py | 290 + .../testpaths/testpath_queryAsyncJobResult.py | 133 + .../testpaths/testpath_revert_snap.py | 161 + .../testpaths/testpath_same_vm_name.py | 188 + .../testpaths/testpath_snapshot_hadrning.py | 2110 +++ .../testpaths/testpath_snapshot_limits.py | 357 + .../testpaths/testpath_stopped_vm.py | 605 + .../testpaths/testpath_storage_migration.py | 3944 ++++ .../integration/testpaths/testpath_usage.py | 3401 ++++ .../testpaths/testpath_uuid_event.py | 202 + .../integration/testpaths/testpath_vmlc.py | 888 + .../testpath_volume_cuncurrent_snapshots.py | 860 + .../testpath_volume_recurring_snap.py | 1046 ++ .../testpaths/testpath_volume_snapshot.py | 976 + .../testpaths/testpath_volumelifecycle.py | 1075 ++ cosmic-core/test/metadata/adapter.xml | 336 + .../delegated_admin_cleanup.xml | 126 + .../delegated_admin_createusers.xml | 312 + .../delegated_admin_verify_part1.xml | 420 + .../delegated_admin_verify_part2.xml | 952 + .../pickuser_domainlevel1_domainlevel2.xml | 122 + ...er_domainlevel1admin_domainlevel1admin.xml | 122 + .../pickuser_domainlevel1admin_rootadmin.xml | 122 + ...ckuser_domainlevel2_child_domainlevel1.xml | 122 + ...ser_domainlevel2_nonchild_domainlevel1.xml | 122 + .../pickuser_domainlevel2_rootadmin1.xml | 122 + .../pickuser_rootadmin1_rootadmin2.xml | 122 + ...ickuser_rootadmin_vs_domainlevel1admin.xml | 122 + cosmic-core/test/metadata/func/commands | 191 + .../metadata/func/directnw_regression.xml | 1910 ++ .../metadata/func/error_events.properties | 32 + cosmic-core/test/metadata/func/expunge.xml | 1207 ++ .../test/metadata/func/external_firewall.xml | 389 + .../test/metadata/func/flatnetwork.xml | 533 + cosmic-core/test/metadata/func/ha.xml | 367 + cosmic-core/test/metadata/func/iso.xml | 907 + .../test/metadata/func/loadbalancers.xml | 2036 +++ .../func/localstorage_volume_test.xml | 153 + cosmic-core/test/metadata/func/mgmtvmsync.xml | 651 + .../test/metadata/func/portforwarding.xml | 1068 ++ .../test/metadata/func/private_templates.xml | 911 + cosmic-core/test/metadata/func/regression.xml | 4443 +++++ .../func/regression_events.properties | 43 + .../test/metadata/func/regression_new.xml | 7672 ++++++++ .../test/metadata/func/regression_test.xml | 3483 ++++ .../test/metadata/func/regression_user.xml | 2382 +++ .../test/metadata/func/regression_works.xml | 8418 +++++++++ .../test/metadata/func/resource_limits.xml | 1894 ++ .../test/metadata/func/roughflatstress.xml | 691 + .../test/metadata/func/roughregression.xml | 6012 +++++++ cosmic-core/test/metadata/func/sanity.xml | 378 + .../test/metadata/func/securitygroups.xml | 770 + .../func/sharedstorage_volume_test.xml | 1374 ++ .../test/metadata/func/snapshot_iso.xml | 646 + cosmic-core/test/metadata/func/snapshots.xml | 827 + .../test/metadata/func/snapshots_contd.xml | 626 + .../metadata/func/srxstresswithportfwd.xml | 595 + cosmic-core/test/metadata/func/static_nat.xml | 916 + .../metadata/func/templatedwnldstress.xml | 757 + .../test/metadata/func/templates_sync.xml | 1389 ++ cosmic-core/test/metadata/func/userapi.xml | 1293 ++ cosmic-core/test/metadata/func/vmapi.xml | 891 + cosmic-core/test/metadata/func/vmsync.xml | 407 + cosmic-core/test/pom.xml | 72 + .../test/scripts/bootstrap-regression.sh | 37 + cosmic-core/test/scripts/build-env.sh | 31 + cosmic-core/test/scripts/certDeleteEC2.sh | 26 + cosmic-core/test/scripts/certSubmitEC2.sh | 26 + cosmic-core/test/scripts/checkLog.sh | 69 + cosmic-core/test/scripts/checkOutOfMemory.sh | 37 + cosmic-core/test/scripts/cleanparallel.sh | 115 + .../test/scripts/deploy-and-run-regression.sh | 48 + cosmic-core/test/scripts/deploy.sh | 172 + cosmic-core/test/scripts/deploycluster.sh | 77 + cosmic-core/test/scripts/executeUserAPI.sh | 34 + cosmic-core/test/scripts/invoke.sh | 28 + cosmic-core/test/scripts/regression.sh | 25 + cosmic-core/test/scripts/run.bat | 18 + cosmic-core/test/scripts/run.sh | 34 + .../test/scripts/script_lock_test/test.sh | 32 + .../scripts/script_lock_test/test_task.sh | 35 + cosmic-core/test/scripts/sign.sh | 26 + cosmic-core/test/scripts/signEC2.sh | 27 + cosmic-core/test/scripts/usage/allocated.sh | 86 + cosmic-core/test/scripts/usage/network.sh | 71 + cosmic-core/test/scripts/usage/running.sh | 87 + .../test/scripts/usage/volume_usage.sh | 161 + cosmic-core/test/scripts/usercloud.properties | 21 + .../test/scripts/xen/corrupttemplate.sh | 51 + .../test/scripts/xen/createfaketemplate.sh | 53 + cosmic-core/test/scripts/xen/killvm.sh | 52 + cosmic-core/test/scripts/xen/listtemplate.sh | 47 + cosmic-core/test/scripts/xen/listvdi.sh | 58 + cosmic-core/test/scripts/xen/listvm.sh | 58 + cosmic-core/test/scripts/xen/ms.sh | 40 + .../test/scripts/xen/removetemplate.sh | 51 + cosmic-core/test/scripts/xen/shutdown.sh | 52 + cosmic-core/test/scripts/xen/sleep.sh | 33 + cosmic-core/test/scripts/xen/ssh.sh | 47 + cosmic-core/test/selenium/ReadMe.txt | 66 + cosmic-core/test/selenium/browser/__init__.py | 16 + cosmic-core/test/selenium/browser/firefox.py | 56 + .../test/selenium/common/Global_Locators.py | 230 + cosmic-core/test/selenium/common/__init__.py | 18 + cosmic-core/test/selenium/common/shared.py | 149 + cosmic-core/test/selenium/cspages/__init__.py | 18 + .../selenium/cspages/accounts/accountspage.py | 175 + .../selenium/cspages/accounts/userspage.py | 146 + cosmic-core/test/selenium/cspages/cspage.py | 21 + .../selenium/cspages/dashboard/__init__.py | 18 + .../cspages/dashboard/dashboardpage.py | 74 + .../test/selenium/cspages/login/__init__.py | 18 + .../test/selenium/cspages/login/loginpage.py | 103 + cosmic-core/test/selenium/cstests/__init__.py | 17 + .../cstests/regressiontests/__init__.py | 17 + .../selenium/cstests/smoketests/__init__.py | 17 + .../cstests/smoketests/adduser_test.py | 103 + .../cstests/smoketests/adduseraccount_test.py | 96 + .../cstests/smoketests/deleteuser_test.py | 100 + .../smoketests/deleteuseraccount_test.py | 91 + .../smoketests/global_settings_test.py | 69 + .../smoketests/login_logout_as_JohnD_test.py | 61 + .../cstests/smoketests/login_logout_test.py | 190 + .../cstests/smoketests/navigation_test.py | 79 + .../selenium/cstests/smoketests/smokecfg.py | 63 + .../test/selenium/lib/Global_Locators.py | 224 + cosmic-core/test/selenium/lib/initialize.py | 46 + .../test/selenium/smoke/Login_and_Accounts.py | 254 + .../test/selenium/smoke/Service_Offering.py | 426 + .../test/selenium/smoke/TemplatesAndISO.py | 244 + .../test/selenium/smoke/VM_lifeCycle.py | 613 + cosmic-core/test/selenium/smoke/main.py | 145 + .../cloud/sample/UserCloudAPIExecutor.java | 188 + .../cloud/test/longrun/BuildGuestNetwork.java | 124 + .../com/cloud/test/longrun/GuestNetwork.java | 108 + .../test/longrun/PerformanceWithAPI.java | 191 + .../java/com/cloud/test/longrun/User.java | 203 + .../cloud/test/longrun/VirtualMachine.java | 96 + .../com/cloud/test/regression/ApiCommand.java | 849 + .../com/cloud/test/regression/ConfigTest.java | 125 + .../test/regression/DelegatedAdminTest.java | 130 + .../com/cloud/test/regression/Deploy.java | 110 + .../cloud/test/regression/EventsApiTest.java | 176 + .../java/com/cloud/test/regression/HA.java | 81 + .../test/regression/LoadBalancingTest.java | 143 + .../test/regression/PortForwardingTest.java | 144 + .../com/cloud/test/regression/SanityTest.java | 87 + .../java/com/cloud/test/regression/Test.java | 89 + .../com/cloud/test/regression/TestCase.java | 139 + .../cloud/test/regression/TestCaseEngine.java | 276 + .../com/cloud/test/regression/VMApiTest.java | 92 + .../java/com/cloud/test/stress/SshTest.java | 91 + .../test/stress/StressTestDirectAttach.java | 1353 ++ .../cloud/test/stress/TestClientWithAPI.java | 2289 +++ .../java/com/cloud/test/stress/WgetTest.java | 151 + .../test/ui/AbstractSeleniumTestCase.java | 56 + .../com/cloud/test/ui/AddAndDeleteAISO.java | 131 + .../cloud/test/ui/AddAndDeleteATemplate.java | 126 + .../com/cloud/test/ui/UIScenarioTest.java | 86 + .../com/cloud/test/utils/ConsoleProxy.java | 111 + .../com/cloud/test/utils/IpSqlGenerator.java | 89 + .../com/cloud/test/utils/ProxyLoadTemp.java | 113 + .../java/com/cloud/test/utils/SignEC2.java | 144 + .../com/cloud/test/utils/SignRequest.java | 112 + .../cloud/test/utils/SqlDataGenerator.java | 49 + .../java/com/cloud/test/utils/SubmitCert.java | 199 + .../java/com/cloud/test/utils/TestClient.java | 386 + .../com/cloud/test/utils/UtilsForTest.java | 210 + cosmic-core/test/systemvm/README.md | 75 + cosmic-core/test/systemvm/__init__.py | 224 + .../test/systemvm/test_hello_systemvm.py | 54 + .../test/systemvm/test_update_config.py | 409 + .../tools/apidoc/XmlToHtmlConverter.java | 112 + cosmic-core/tools/apidoc/build-apidoc.sh | 91 + cosmic-core/tools/apidoc/gen_toc.py | 306 + .../tools/apidoc/generateadmincommands.xsl | 164 + cosmic-core/tools/apidoc/generatecommand.xsl | 195 + .../tools/apidoc/generatecustomcommand.xsl | 65 + .../apidoc/generatedomainadmincommands.xsl | 159 + .../tools/apidoc/generategenericcommand.xsl | 57 + cosmic-core/tools/apidoc/generatetoc.xsl | 1146 ++ .../tools/apidoc/generatetoc_footer.xsl | 39 + .../tools/apidoc/generatetoc_header.xsl | 71 + .../tools/apidoc/generateusercommands.xsl | 157 + .../tools/apidoc/images/api_bullets.gif | Bin 0 -> 45 bytes .../tools/apidoc/images/back_button.gif | Bin 0 -> 870 bytes .../tools/apidoc/images/back_button_hover.gif | Bin 0 -> 868 bytes .../tools/apidoc/images/cloudstack.png | Bin 0 -> 8575 bytes cosmic-core/tools/apidoc/includes/main.css | 1092 ++ cosmic-core/tools/apidoc/pom.xml | 120 + cosmic-core/tools/pom.xml | 21 + cosmic-core/tools/transifex/.tx/config | 91 + .../tools/transifex/README-transifex.txt | 71 + .../tools/transifex/sync-transifex-ui.sh | 160 + cosmic-core/tools/whisker/LICENSE | 4744 +++++ cosmic-core/tools/whisker/NOTICE | 697 + .../whisker/descriptor-for-packaging.xml | 3012 ++++ cosmic-core/tools/whisker/descriptor.xml | 2674 +++ cosmic-core/usage/conf/db.properties.in | 29 + .../usage/conf/log4j-cloud_usage.xml.in | 86 + cosmic-core/usage/pom.xml | 192 + .../java/com/cloud/usage/StorageTypes.java | 23 + .../java/com/cloud/usage/UsageManager.java | 25 + .../com/cloud/usage/UsageManagerImpl.java | 1844 ++ .../com/cloud/usage/UsageSanityChecker.java | 284 + .../java/com/cloud/usage/UsageServer.java | 111 + .../usage/parser/IPAddressUsageParser.java | 197 + .../usage/parser/LoadBalancerUsageParser.java | 181 + .../parser/NetworkOfferingUsageParser.java | 195 + .../usage/parser/NetworkUsageParser.java | 186 + .../parser/PortForwardingUsageParser.java | 181 + .../parser/SecurityGroupUsageParser.java | 187 + .../usage/parser/StorageUsageParser.java | 235 + .../com/cloud/usage/parser/UsageParser.java | 38 + .../usage/parser/VMInstanceUsageParser.java | 248 + .../usage/parser/VMSnapshotUsageParser.java | 154 + .../usage/parser/VPNUserUsageParser.java | 182 + .../cloud/usage/parser/VmDiskUsageParser.java | 220 + .../cloud/usage/parser/VolumeUsageParser.java | 210 + .../resources/usageApplicationContext.xml | 48 + .../com/cloud/usage/UsageManagerTest.java | 113 + .../usage/UsageManagerTestConfiguration.java | 96 + .../com/cloud/usage/UsageSanityCheckerIT.java | 159 + .../cloud/usage/UsageSanityCheckerTest.java | 68 + .../resources/UsageManagerTestContext.xml | 42 + .../usage/src/test/resources/cloud1.xml | 24 + .../usage/src/test/resources/cloud2.xml | 24 + .../usage/src/test/resources/cloud3.xml | 13 + .../usage/src/test/resources/cloud_usage1.xml | 26 + .../usage/src/test/resources/cloud_usage2.xml | 43 + .../usage/src/test/resources/cloud_usage3.xml | 13 + .../usage/src/test/resources/db.properties | 50 + cosmic-core/utils/bindir/cloud-gitrevs.in | 29 + cosmic-core/utils/bindir/cloud-sccs.in | 29 + cosmic-core/utils/conf/db.properties | 53 + cosmic-core/utils/conf/log4j-vmops.xml | 96 + cosmic-core/utils/pom.xml | 198 + .../InvalidParameterValueException.java | 29 + .../main/java/com/cloud/maint/Version.java | 77 + .../java/com/cloud/utils/ActionDelegate.java | 24 + .../com/cloud/utils/AutoCloseableUtil.java | 37 + .../com/cloud/utils/CloudResourceBundle.java | 48 + .../cloud/utils/ConstantTimeComparator.java | 42 + .../main/java/com/cloud/utils/DateUtil.java | 274 + .../java/com/cloud/utils/EncryptionUtil.java | 74 + .../main/java/com/cloud/utils/EnumUtils.java | 58 + .../java/com/cloud/utils/ExecutionResult.java | 46 + .../main/java/com/cloud/utils/FileUtil.java | 32 + .../main/java/com/cloud/utils/HttpUtils.java | 116 + .../java/com/cloud/utils/IteratorUtil.java | 72 + .../main/java/com/cloud/utils/Journal.java | 104 + .../main/java/com/cloud/utils/LogUtils.java | 46 + .../java/com/cloud/utils/MethodCapturer.java | 113 + .../java/com/cloud/utils/NumbersUtil.java | 139 + .../src/main/java/com/cloud/utils/Pair.java | 87 + .../com/cloud/utils/PasswordGenerator.java | 99 + .../main/java/com/cloud/utils/Predicate.java | 24 + .../java/com/cloud/utils/ProcessUtil.java | 113 + .../main/java/com/cloud/utils/Profiler.java | 88 + .../java/com/cloud/utils/PropertiesUtil.java | 197 + .../java/com/cloud/utils/ReflectUtil.java | 213 + .../java/com/cloud/utils/ReflectionUse.java | 31 + .../com/cloud/utils/SerialVersionUID.java | 72 + .../java/com/cloud/utils/StringUtils.java | 323 + .../main/java/com/cloud/utils/SwiftUtil.java | 240 + .../main/java/com/cloud/utils/Ternary.java | 85 + .../main/java/com/cloud/utils/UriUtils.java | 394 + .../utils/UsernamePasswordValidator.java | 49 + .../main/java/com/cloud/utils/UuidUtils.java | 34 + .../cloud/utils/backoff/BackoffAlgorithm.java | 38 + .../backoff/impl/ConstantTimeBackoff.java | 99 + .../impl/ConstantTimeBackoffMBean.java | 35 + .../utils/cisco/n1kv/vsm/NetconfHelper.java | 339 + .../cloud/utils/cisco/n1kv/vsm/PolicyMap.java | 34 + .../utils/cisco/n1kv/vsm/VsmCommand.java | 988 + .../utils/cisco/n1kv/vsm/VsmOkResponse.java | 44 + .../cisco/n1kv/vsm/VsmPolicyMapResponse.java | 85 + .../utils/cisco/n1kv/vsm/VsmResponse.java | 223 + .../com/cloud/utils/component/Adapter.java | 27 + .../cloud/utils/component/AdapterBase.java | 40 + .../cloud/utils/component/AdapterList.java | 37 + .../utils/component/ComponentContext.java | 283 + .../ComponentInstantiationPostProcessor.java | 150 + .../utils/component/ComponentLifecycle.java | 64 + .../component/ComponentLifecycleBase.java | 88 + .../ComponentMethodInterceptable.java | 27 + .../component/ComponentMethodInterceptor.java | 32 + .../component/ComponentNamingPolicy.java | 65 + .../com/cloud/utils/component/Manager.java | 27 + .../cloud/utils/component/ManagerBase.java | 58 + .../java/com/cloud/utils/component/Named.java | 26 + .../utils/component/PluggableService.java | 29 + .../com/cloud/utils/component/Registry.java | 51 + .../component/SystemIntegrityChecker.java | 30 + .../utils/concurrency/NamedThreadFactory.java | 38 + .../cloud/utils/concurrency/Scheduler.java | 31 + .../concurrency/SynchronizationEvent.java | 90 + .../cloud/utils/concurrency/TestClock.java | 161 + .../cloud/utils/crypt/DBEncryptionUtil.java | 88 + .../crypt/EncryptionSecretKeyChecker.java | 147 + .../crypt/EncryptionSecretKeySender.java | 65 + .../java/com/cloud/utils/crypt/RSAHelper.java | 91 + .../java/com/cloud/utils/db/DbProperties.java | 110 + .../com/cloud/utils/db/EntityManager.java | 84 + .../java/com/cloud/utils/db/UUIDManager.java | 48 + .../com/cloud/utils/encoding/URLEncoder.java | 113 + .../com/cloud/utils/events/EventArgs.java | 44 + .../cloud/utils/events/SubscriptionMgr.java | 165 + .../utils/exception/CSExceptionErrorCode.java | 101 + .../exception/CloudRuntimeException.java | 141 + .../cloud/utils/exception/ErrorContext.java | 31 + .../utils/exception/ExceptionProxyObject.java | 55 + .../cloud/utils/exception/ExceptionUtil.java | 54 + .../utils/exception/ExecutionException.java | 49 + .../HypervisorVersionChangedException.java | 35 + .../exception/NioConnectionException.java | 48 + .../exception/TaskExecutionException.java | 48 + .../java/com/cloud/utils/fsm/ChangeEvent.java | 24 + .../java/com/cloud/utils/fsm/FiniteState.java | 58 + .../com/cloud/utils/fsm/FiniteState2.java | 34 + .../cloud/utils/fsm/FiniteStateObject.java | 25 + .../utils/fsm/NoTransitionException.java | 36 + .../main/java/com/cloud/utils/fsm/State.java | 27 + .../java/com/cloud/utils/fsm/StateDao.java | 25 + .../com/cloud/utils/fsm/StateListener.java | 43 + .../com/cloud/utils/fsm/StateMachine.java | 147 + .../com/cloud/utils/fsm/StateMachine2.java | 261 + .../java/com/cloud/utils/fsm/StateObject.java | 27 + .../utils/log/CglibThrowableRenderer.java | 82 + .../java/com/cloud/utils/mgmt/JmxUtil.java | 90 + .../com/cloud/utils/mgmt/ManagementBean.java | 27 + .../utils/mgmt/PropertyMapDynamicBean.java | 120 + .../java/com/cloud/utils/net/HTTPUtils.java | 144 + .../src/main/java/com/cloud/utils/net/Ip.java | 98 + .../java/com/cloud/utils/net/Ip4Address.java | 80 + .../java/com/cloud/utils/net/MacAddress.java | 368 + .../java/com/cloud/utils/net/NetUtils.java | 1586 ++ .../java/com/cloud/utils/net/NfsUtils.java | 55 + .../main/java/com/cloud/utils/net/Proxy.java | 69 + .../java/com/cloud/utils/net/UrlUtil.java | 63 + .../nvp/plugin/NiciraNvpApiVersion.java | 46 + .../com/cloud/utils/nio/HandlerFactory.java | 28 + .../main/java/com/cloud/utils/nio/Link.java | 568 + .../java/com/cloud/utils/nio/NioClient.java | 122 + .../com/cloud/utils/nio/NioConnection.java | 502 + .../java/com/cloud/utils/nio/NioServer.java | 98 + .../main/java/com/cloud/utils/nio/Task.java | 86 + .../com/cloud/utils/nio/TrustAllManager.java | 45 + .../com/cloud/utils/rest/BasicRestClient.java | 119 + .../utils/rest/CloudstackRESTException.java | 33 + .../cloud/utils/rest/HttpClientHelper.java | 91 + .../com/cloud/utils/rest/HttpConstants.java | 34 + .../com/cloud/utils/rest/HttpMethods.java | 41 + .../utils/rest/HttpStatusCodeHelper.java | 34 + .../utils/rest/HttpUriRequestBuilder.java | 119 + .../utils/rest/RESTServiceConnector.java | 167 + .../java/com/cloud/utils/rest/RestClient.java | 31 + .../cloud/utils/script/OutputInterpreter.java | 142 + .../java/com/cloud/utils/script/Script.java | 507 + .../java/com/cloud/utils/script/Script2.java | 70 + .../utils/security/CertificateHelper.java | 166 + .../com/cloud/utils/ssh/SSHCmdHelper.java | 182 + .../com/cloud/utils/ssh/SSHKeysHelper.java | 115 + .../com/cloud/utils/ssh/SshException.java | 30 + .../java/com/cloud/utils/ssh/SshHelper.java | 282 + .../com/cloud/utils/storage/QCOW2Utils.java | 60 + .../cloud/utils/storage/S3/ClientOptions.java | 42 + .../utils/storage/S3/FileNamingStrategy.java | 25 + .../storage/S3/ObjectNamingStrategy.java | 27 + .../com/cloud/utils/storage/S3/S3Utils.java | 220 + .../storage/encoding/DecodedDataObject.java | 56 + .../storage/encoding/DecodedDataStore.java | 68 + .../cloud/utils/storage/encoding/Decoder.java | 67 + .../utils/storage/encoding/EncodingType.java | 32 + .../com/cloud/utils/time/InaccurateClock.java | 103 + .../utils/time/InaccurateClockMBean.java | 28 + .../com/cloud/utils/xmlobject/XmlObject.java | 216 + .../utils/xmlobject/XmlObjectParser.java | 130 + .../utils/graphite/GraphiteClient.java | 123 + .../utils/graphite/GraphiteException.java | 31 + .../utils/hypervisor/HypervisorUtils.java | 70 + .../utils/identity/ManagementServerNode.java | 61 + .../utils/imagestore/ImageStoreUtil.java | 102 + .../cloudstack/utils/security/SSLUtils.java | 60 + .../security/SecureSSLSocketFactory.java | 126 + .../cloudstack/utils/usage/UsageUtils.java | 24 + .../ssl/EasySSLProtocolSocketFactory.java | 211 + .../contrib/ssl/EasyX509TrustManager.java | 110 + .../utils/src/main/resources/cloud.keystore | Bin 0 -> 1316 bytes .../java/com/cloud/utils/DateUtilTest.java | 60 + .../test/java/com/cloud/utils/DummyImpl.java | 31 + .../java/com/cloud/utils/DummyInterface.java | 24 + .../com/cloud/utils/DummyPremiumImpl.java | 28 + .../java/com/cloud/utils/HttpUtilsTest.java | 95 + .../java/com/cloud/utils/NumbersUtilTest.java | 47 + .../cloud/utils/PasswordGeneratorTest.java | 65 + .../java/com/cloud/utils/ProcessUtilTest.java | 76 + .../com/cloud/utils/PropertiesUtilsTest.java | 64 + .../java/com/cloud/utils/ReflectUtilTest.java | 150 + .../test/java/com/cloud/utils/ScriptTest.java | 135 + .../java/com/cloud/utils/StringUtilsTest.java | 253 + .../java/com/cloud/utils/TernaryTest.java | 34 + .../java/com/cloud/utils/TestProfiler.java | 97 + .../java/com/cloud/utils/UriUtilsTest.java | 60 + .../java/com/cloud/utils/UuidUtilsTest.java | 42 + .../backoff/impl/ConstantTimeBackoffTest.java | 112 + .../EncryptionSecretKeyCheckerTest.java | 45 + .../com/cloud/utils/crypto/RSAHelperTest.java | 54 + .../cloud/utils/encoding/UrlEncoderTest.java | 33 + .../utils/exception/ExceptionUtilTest.java | 52 + .../utils/log/CglibThrowableRendererTest.java | 85 + .../com/cloud/utils/net/Ip4AddressTest.java | 40 + .../test/java/com/cloud/utils/net/IpTest.java | 63 + .../com/cloud/utils/net/MacAddressTest.java | 60 + .../com/cloud/utils/net/NetUtilsTest.java | 530 + .../cloud/utils/rest/BasicRestClientTest.java | 110 + .../utils/rest/HttpClientHelperTest.java | 38 + .../cloud/utils/rest/HttpRequestMatcher.java | 141 + .../utils/rest/HttpStatusCodeHelperTest.java | 59 + .../utils/rest/HttpUriRequestBuilderTest.java | 115 + .../rest/HttpUriRequestMethodMatcher.java | 44 + .../utils/rest/HttpUriRequestPathMatcher.java | 43 + .../rest/HttpUriRequestPayloadMatcher.java | 62 + .../rest/HttpUriRequestQueryMatcher.java | 48 + .../utils/rest/RESTServiceConnectorTest.java | 323 + .../cloud/utils/ssh/SSHKeysHelperTest.java | 73 + .../com/cloud/utils/ssh/SshHelperTest.java | 151 + .../cloud/utils/storage/QCOW2UtilsTest.java | 121 + .../utils/testcase/Log4jEnabledTestCase.java | 58 + .../com/cloud/utils/testcase/NioTest.java | 226 + .../cloud/utils/xmlobject/TestXmlObject.java | 53 + .../cloud/utils/xmlobject/TestXmlObject2.java | 54 + .../utils/hypervisor/HypervisorUtilsTest.java | 112 + .../utils/imagestore/ImageStoreUtilTest.java | 54 + .../com/cloud/utils/QualifierTestContext.xml | 38 + .../db/transactionContextBuilderTest.xml | 48 + .../utils/src/test/resources/log4j.xml | 118 + .../utils/src/test/resources/testContext.xml | 52 + cosmic-plugin-event-bus-rabbitmq/LICENSE | 201 + cosmic-plugin-event-bus-rabbitmq/README.md | 1 + cosmic-plugin-event-bus-rabbitmq/pom.xml | 32 + .../mom/rabbitmq/RabbitMQEventBus.java | 518 + .../mom/rabbitmq/RabbitMQEventBusTest.java | 15 + cosmic-plugin-hypervisor-kvm/LICENSE | 201 + cosmic-plugin-hypervisor-kvm/README.md | 1 + cosmic-plugin-hypervisor-kvm/pom.xml | 118 + .../java/com/cloud/ha/KvmInvestigator.java | 94 + .../kvm/resource/BridgeVifDriver.java | 310 + .../kvm/resource/KvmGuestOsMapper.java | 131 + .../hypervisor/kvm/resource/KvmHaBase.java | 168 + .../hypervisor/kvm/resource/KvmHaChecker.java | 62 + .../hypervisor/kvm/resource/KvmHaMonitor.java | 155 + .../kvm/resource/LibvirtCapXmlParser.java | 116 + .../resource/LibvirtComputingResource.java | 3449 ++++ .../kvm/resource/LibvirtConnection.java | 79 + .../kvm/resource/LibvirtDomainXmlParser.java | 274 + .../kvm/resource/LibvirtNetworkDef.java | 153 + .../kvm/resource/LibvirtSecretDef.java | 90 + .../kvm/resource/LibvirtStoragePoolDef.java | 172 + .../resource/LibvirtStoragePoolXmlParser.java | 111 + .../kvm/resource/LibvirtStorageVolumeDef.java | 94 + .../LibvirtStorageVolumeXmlParser.java | 64 + .../hypervisor/kvm/resource/LibvirtVmDef.java | 1577 ++ .../kvm/resource/LibvirtXmlParser.java | 58 + .../kvm/resource/MigrateKvmAsync.java | 40 + .../hypervisor/kvm/resource/OvsVifDriver.java | 175 + .../hypervisor/kvm/resource/VifDriver.java | 23 + .../kvm/resource/VifDriverBase.java | 49 + .../LibvirtAttachIsoCommandWrapper.java | 40 + .../LibvirtBackupSnapshotCommandWrapper.java | 187 + ...rtCheckConsoleProxyLoadCommandWrapper.java | 25 + .../LibvirtCheckHealthCommandWrapper.java | 18 + .../LibvirtCheckNetworkCommandWrapper.java | 41 + .../LibvirtCheckOnHostCommandWrapper.java | 48 + .../LibvirtCheckSshCommandWrapper.java | 42 + ...virtCheckVirtualMachineCommandWrapper.java | 36 + ...virtCleanupNetworkRulesCommandWrapper.java | 18 + ...LibvirtConsoleProxyLoadCommandWrapper.java | 62 + .../LibvirtCopyVolumeCommandWrapper.java | 76 + .../wrapper/LibvirtCreateCommandWrapper.java | 72 + ...ateTemplateFromSnapshotCommandWrapper.java | 101 + ...ivateTemplateFromVolumeCommandWrapper.java | 164 + ...ibvirtCreateStoragePoolCommandWrapper.java | 18 + ...reateVolumeFromSnapshotCommandWrapper.java | 54 + ...ibvirtDeleteStoragePoolCommandWrapper.java | 28 + .../wrapper/LibvirtDestroyCommandWrapper.java | 35 + .../wrapper/LibvirtFenceCommandWrapper.java | 63 + .../LibvirtGetHostStatsCommandWrapper.java | 44 + .../LibvirtGetStorageStatsCommandWrapper.java | 31 + .../LibvirtGetVmDiskStatsCommandWrapper.java | 55 + .../LibvirtGetVmStatsCommandWrapper.java | 56 + .../LibvirtGetVncPortCommandWrapper.java | 33 + .../LibvirtMaintainCommandWrapper.java | 22 + .../LibvirtManageSnapshotCommandWrapper.java | 141 + .../wrapper/LibvirtMigrateCommandWrapper.java | 177 + .../LibvirtModifySshKeysCommandWrapper.java | 109 + ...ibvirtModifyStoragePoolCommandWrapper.java | 41 + .../LibvirtNetworkElementCommandWrapper.java | 23 + ...irtNetworkRulesSystemVmCommandWrapper.java | 39 + ...tworkRulesVmSecondaryIpCommandWrapper.java | 40 + .../LibvirtNetworkUsageCommandWrapper.java | 49 + .../LibvirtPingTestCommandWrapper.java | 55 + .../wrapper/LibvirtPlugNicCommandWrapper.java | 74 + ...virtPrepareForMigrationCommandWrapper.java | 81 + ...tPrimaryStorageDownloadCommandWrapper.java | 79 + .../LibvirtPvlanSetupCommandWrapper.java | 96 + .../wrapper/LibvirtReadyCommandWrapper.java | 21 + .../wrapper/LibvirtRebootCommandWrapper.java | 47 + .../LibvirtRebootRouterCommandWrapper.java | 35 + .../wrapper/LibvirtRequestWrapper.java | 61 + .../LibvirtResizeVolumeCommandWrapper.java | 128 + .../LibvirtRevertSnapshotCommandWrapper.java | 83 + ...bvirtSecurityGroupRulesCommandWrapper.java | 59 + .../wrapper/LibvirtStartCommandWrapper.java | 133 + .../wrapper/LibvirtStopCommandWrapper.java | 75 + ...LibvirtStorageSubSystemCommandWrapper.java | 25 + .../LibvirtUnPlugNicCommandWrapper.java | 69 + ...bvirtUpdateHostPasswordCommandWrapper.java | 35 + .../LibvirtUpgradeSnapshotCommandWrapper.java | 21 + .../wrapper/LibvirtUtilitiesHelper.java | 83 + ...rtWatchConsoleProxyLoadCommandWrapper.java | 29 + .../kvm/storage/IscsiAdmStorageAdaptor.java | 375 + .../kvm/storage/IscsiAdmStoragePool.java | 155 + .../kvm/storage/KvmPhysicalDisk.java | 84 + .../kvm/storage/KvmStoragePool.java | 58 + .../kvm/storage/KvmStoragePoolManager.java | 384 + .../kvm/storage/KvmStorageProcessor.java | 1257 ++ .../kvm/storage/KvmStorageResource.java | 69 + .../kvm/storage/KvmVirtualDisk.java | 5 + .../kvm/storage/LibvirtStorageAdaptor.java | 1325 ++ .../kvm/storage/LibvirtStoragePool.java | 258 + .../kvm/storage/ManagedNfsStorageAdaptor.java | 324 + .../kvm/storage/StorageAdaptor.java | 61 + .../kvm/storage/StorageAdaptorInfo.java | 16 + .../cloudstack/utils/linux/CpuStat.java | 90 + .../cloudstack/utils/linux/MemStat.java | 55 + .../apache/cloudstack/utils/qemu/QemuImg.java | 254 + .../utils/qemu/QemuImgException.java | 9 + .../cloudstack/utils/qemu/QemuImgFile.java | 55 + .../cloudstack/kvm-compute/module.properties | 2 + .../spring-kvm-compute-context.xml | 11 + .../LibvirtComputingResourceTest.java | 4697 +++++ .../resource/LibvirtDomainXMLParserTest.java | 196 + .../kvm/resource/LibvirtSecretDefTest.java | 39 + .../resource/LibvirtStoragePoolDefTest.java | 71 + .../kvm/resource/LibvirtVMDefTest.java | 130 + .../kvm/resource/LibvirtVifDriverTest.java | 199 + .../wrapper/LibvirtUtilitiesHelperTest.java | 42 + .../kvm/storage/KvmPhysicalDiskTest.java | 37 + .../kvm/storage/KvmStorageProcessorTest.java | 23 + .../kvm/storage/LibvirtStoragePoolTest.java | 78 + .../cloudstack/utils/linux/MemStatTest.java | 38 + .../utils/qemu/QemuImgFileTest.java | 46 + .../cloudstack/utils/qemu/QemuImgTest.java | 301 + cosmic-plugin-hypervisor-ovm3/LICENSE | 201 + cosmic-plugin-hypervisor-ovm3/README.md | 1 + cosmic-plugin-hypervisor-ovm3/pom.xml | 144 + .../sonar-project.properties | 36 + .../java/com/cloud/ha/Ovm3Investigator.java | 84 + .../ovm3/objects/CloudstackPlugin.java | 190 + .../hypervisor/ovm3/objects/Cluster.java | 63 + .../cloud/hypervisor/ovm3/objects/Common.java | 42 + .../hypervisor/ovm3/objects/Connection.java | 196 + .../cloud/hypervisor/ovm3/objects/Linux.java | 440 + .../hypervisor/ovm3/objects/Network.java | 329 + .../cloud/hypervisor/ovm3/objects/Ntp.java | 119 + .../ovm3/objects/Ovm3ResourceException.java | 40 + .../hypervisor/ovm3/objects/OvmObject.java | 249 + .../cloud/hypervisor/ovm3/objects/Pool.java | 253 + .../hypervisor/ovm3/objects/PoolOcfS2.java | 149 + .../cloud/hypervisor/ovm3/objects/Remote.java | 33 + .../hypervisor/ovm3/objects/Repository.java | 321 + .../ovm3/objects/RpcTypeFactory.java | 90 + .../ovm3/objects/StoragePlugin.java | 873 + .../cloud/hypervisor/ovm3/objects/Xen.java | 931 + .../ovm3/resources/Ovm3Discoverer.java | 402 + .../ovm3/resources/Ovm3FenceBuilder.java | 116 + .../ovm3/resources/Ovm3HypervisorGuru.java | 118 + .../resources/Ovm3HypervisorResource.java | 586 + .../ovm3/resources/Ovm3StorageProcessor.java | 737 + .../resources/Ovm3VirtualRoutingResource.java | 182 + .../resources/helpers/Ovm3Configuration.java | 462 + .../helpers/Ovm3HypervisorNetwork.java | 250 + .../helpers/Ovm3HypervisorSupport.java | 634 + .../resources/helpers/Ovm3StoragePool.java | 617 + .../helpers/Ovm3VirtualRoutingSupport.java | 204 + .../resources/helpers/Ovm3VmGuestTypes.java | 82 + .../ovm3/resources/helpers/Ovm3VmSupport.java | 437 + .../cloudstack/ovm3-compute/module.properties | 18 + .../spring-ovm3-compute-context.xml | 41 + .../ovm3-discoverer/module.properties | 18 + .../spring-ovm3-discoverer-context.xml | 34 + .../ovm3/objects/CloudStackPluginTest.java | 329 + .../hypervisor/ovm3/objects/CommonTest.java | 52 + .../ovm3/objects/ConnectionTest.java | 165 + .../hypervisor/ovm3/objects/LinuxTest.java | 645 + .../hypervisor/ovm3/objects/NetworkTest.java | 344 + .../hypervisor/ovm3/objects/NtpTest.java | 99 + .../ovm3/objects/PoolOCFS2Test.java | 109 + .../hypervisor/ovm3/objects/PoolTest.java | 177 + .../hypervisor/ovm3/objects/RemoteTest.java | 38 + .../ovm3/objects/RepositoryTest.java | 175 + .../ovm3/objects/StoragePluginTest.java | 361 + .../hypervisor/ovm3/objects/XenTest.java | 729 + .../ovm3/objects/XmlTestResultTest.java | 134 + .../resources/Ovm3HypervisorResourceTest.java | 358 + .../resources/Ovm3StorageProcessorTest.java | 319 + .../Ovm3VirtualRoutingResourceTest.java | 192 + .../helpers/Ovm3ConfigurationTest.java | 115 + .../resources/helpers/Ovm3GuestTypesTest.java | 33 + .../helpers/Ovm3HypervisorNetworkTest.java | 134 + .../helpers/Ovm3HypervisorSupportTest.java | 279 + .../Ovm3VirtualRoutingSupportTest.java | 96 + .../resources/helpers/Ovm3VmSupportTest.java | 139 + .../ovm3/support/Ovm3SupportTest.java | 113 + .../src/test/resources/log4j.properties | 25 + .../test/resources/scripts/clean_master.sh | 43 + .../src/test/resources/scripts/clean_slave.sh | 32 + .../resources/scripts/create_pool_cluster.py | 271 + .../src/test/resources/scripts/info.py | 111 + .../src/test/resources/scripts/password.py | 57 + .../src/test/resources/scripts/repo_pool.py | 186 + .../src/test/resources/scripts/simple_pool.py | 209 + .../src/test/resources/scripts/socat.sh | 19 + .../src/test/resources/scripts/tail.sh | 19 + cosmic-plugin-hypervisor-xenserver/LICENSE | 201 + cosmic-plugin-hypervisor-xenserver/README.md | 1 + cosmic-plugin-hypervisor-xenserver/pom.xml | 70 + .../java/com/cloud/ha/XenServerFencer.java | 128 + .../com/cloud/hypervisor/XenServerGuru.java | 219 + .../discoverer/XcpServerDiscoverer.java | 689 + .../xenserver/resource/CitrixHelper.java | 250 + .../resource/CitrixResourceBase.java | 5420 ++++++ .../xenserver/resource/XcpOssResource.java | 55 + .../xenserver/resource/XcpServerResource.java | 107 + .../resource/XenServer56FP1Resource.java | 51 + .../resource/XenServer56Resource.java | 136 + .../resource/XenServer56SP2Resource.java | 31 + .../resource/XenServer600Resource.java | 30 + .../resource/XenServer610Resource.java | 88 + .../resource/XenServer620Resource.java | 73 + .../resource/XenServer620SP1Resource.java | 140 + .../resource/XenServer650Resource.java | 32 + .../resource/XenServerConnectionPool.java | 506 + .../resource/XenServerStorageProcessor.java | 1748 ++ .../resource/Xenserver625Resource.java | 108 + .../Xenserver625StorageProcessor.java | 1155 ++ .../hypervisor/xenserver/resource/XsHost.java | 212 + .../xenserver/resource/XsLocalNetwork.java | 92 + .../XcpServerNetworkUsageCommandWrapper.java | 55 + .../XenServer56CheckOnHostCommandWrapper.java | 51 + .../xen56/XenServer56FenceCommandWrapper.java | 74 + ...XenServer56NetworkUsageCommandWrapper.java | 105 + .../XenServer56FP1FenceCommandWrapper.java | 95 + ...nServer610MigrateVolumeCommandWrapper.java | 74 + ...er610MigrateWithStorageCommandWrapper.java | 140 + ...rateWithStorageCompleteCommandWrapper.java | 84 + ...grateWithStorageReceiveCommandWrapper.java | 107 + ...0MigrateWithStorageSendCommandWrapper.java | 154 + ...Server620SP1GetGPUStatsCommandWrapper.java | 54 + .../CitrixAttachIsoCommandWrapper.java | 137 + ...achOrDettachConfigDriveCommandWrapper.java | 96 + ...ixCheckConsoleProxyLoadCommandWrapper.java | 43 + .../CitrixCheckHealthCommandWrapper.java | 37 + .../CitrixCheckNetworkCommandWrapper.java | 97 + .../CitrixCheckOnHostCommandWrapper.java | 36 + .../xenbase/CitrixCheckSshCommandWrapper.java | 66 + ...trixCheckVirtualMachineCommandWrapper.java | 51 + .../CitrixCleanupNetworkRulesCmdWrapper.java | 57 + ...ixClusterVMMetaDataSyncCommandWrapper.java | 60 + .../CitrixConsoleProxyLoadCommandWrapper.java | 83 + .../xenbase/CitrixCreateCommandWrapper.java | 85 + ...CitrixCreateStoragePoolCommandWrapper.java | 60 + .../CitrixCreateVMSnapshotCommandWrapper.java | 191 + ...CitrixDeleteStoragePoolCommandWrapper.java | 55 + .../CitrixDeleteVMSnapshotCommandWrapper.java | 93 + .../xenbase/CitrixDestroyCommandWrapper.java | 86 + .../CitrixGetHostStatsCommandWrapper.java | 51 + .../CitrixGetStorageStatsCommandWrapper.java | 72 + .../CitrixGetVmDiskStatsCommandWrapper.java | 36 + .../CitrixGetVmIpAddressCommandWrapper.java | 84 + .../CitrixGetVmStatsCommandWrapper.java | 85 + .../CitrixGetVncPortCommandWrapper.java | 59 + .../xenbase/CitrixMaintainCommandWrapper.java | 78 + .../xenbase/CitrixMigrateCommandWrapper.java | 99 + .../CitrixModifySshKeysCommandWrapper.java | 35 + ...CitrixModifyStoragePoolCommandWrapper.java | 99 + .../CitrixNetworkElementCommandWrapper.java | 37 + ...rixNetworkRulesSystemVmCommandWrapper.java | 47 + ...tworkRulesVmSecondaryIpCommandWrapper.java | 46 + ...itrixPerformanceMonitorCommandWrapper.java | 43 + .../xenbase/CitrixPingTestCommandWrapper.java | 49 + .../xenbase/CitrixPlugNicCommandWrapper.java | 95 + ...trixPrepareForMigrationCommandWrapper.java | 73 + ...xPrimaryStorageDownloadCommandWrapper.java | 86 + .../CitrixPvlanSetupCommandWrapper.java | 95 + .../xenbase/CitrixReadyCommandWrapper.java | 78 + .../xenbase/CitrixRebootCommandWrapper.java | 71 + .../CitrixRebootRouterCommandWrapper.java | 54 + .../wrapper/xenbase/CitrixRequestWrapper.java | 124 + .../CitrixResizeVolumeCommandWrapper.java | 55 + ...itrixRevertToVMSnapshotCommandWrapper.java | 113 + .../xenbase/CitrixScaleVmCommandWrapper.java | 109 + ...itrixSecurityGroupRulesCommandWrapper.java | 64 + .../xenbase/CitrixSetupCommandWrapper.java | 203 + .../xenbase/CitrixStartCommandWrapper.java | 199 + .../xenbase/CitrixStopCommandWrapper.java | 176 + .../CitrixStorageSubSystemCommandWrapper.java | 38 + .../CitrixUnPlugNicCommandWrapper.java | 75 + ...itrixUpdateHostPasswordCommandWrapper.java | 61 + .../CitrixUpgradeSnapshotCommandWrapper.java | 67 + ...ixWatchConsoleProxyLoadCommandWrapper.java | 43 + .../xenbase/XenServerUtilitiesHelper.java | 45 + .../xenserver/XenServerResourceNewBase.java | 250 + .../xenserver/XenserverConfigs.java | 25 + .../XenServerStorageMotionStrategy.java | 250 + .../xenserver-compute/module.properties | 18 + .../spring-xenserver-compute-context.xml | 28 + .../xenserver-discoverer/module.properties | 18 + .../spring-xenserver-discoverer-context.xml | 25 + .../xenserver/resource/CitrixHelperTest.java | 50 + .../resource/CitrixResourceBaseTest.java | 52 + .../resource/XcpOssResourceTest.java | 50 + .../resource/XcpServerResourceTest.java | 51 + .../resource/XenServer56FP1ResourceTest.java | 48 + .../resource/XenServer56ResourceTest.java | 49 + .../resource/XenServer56SP2ResourceTest.java | 48 + .../resource/XenServer600ResourceTest.java | 48 + .../resource/XenServer625ResourceTest.java | 105 + .../resource/XenServer650ResourceTest.java | 49 + .../xenbase/CitrixRequestWrapperTest.java | 1518 ++ .../wrapper/xenbase/XcpServerWrapperTest.java | 116 + .../xenbase/XenServer56FP1WrapperTest.java | 65 + .../xenbase/XenServer56WrapperTest.java | 177 + .../xenbase/XenServer610WrapperTest.java | 528 + .../xenbase/XenServer620SP1WrapperTest.java | 115 + .../xenbase/XenServer620WrapperTest.java | 51 + .../README.md | 1 + cosmic-plugin-user-authenticator-ldap/pom.xml | 139 + .../cloudstack/api/command/LDAPConfigCmd.java | 278 + .../cloudstack/api/command/LDAPRemoveCmd.java | 79 + .../api/command/LdapAddConfigurationCmd.java | 97 + .../api/command/LdapCreateAccountCmd.java | 181 + .../command/LdapDeleteConfigurationCmd.java | 77 + .../api/command/LdapImportUsersCmd.java | 238 + .../api/command/LdapListConfigurationCmd.java | 107 + .../api/command/LdapListUsersCmd.java | 123 + .../api/command/LdapUserSearchCmd.java | 97 + .../api/command/LinkDomainToLdapCmd.java | 116 + .../api/response/LDAPConfigResponse.java | 115 + .../api/response/LDAPRemoveResponse.java | 30 + .../response/LdapConfigurationResponse.java | 62 + .../api/response/LdapUserResponse.java | 110 + .../response/LinkDomainToLdapResponse.java | 79 + .../ldap/ADLdapUserManagerImpl.java | 105 + .../ldap/DistinguishedNameParser.java | 45 + .../cloudstack/ldap/LdapAuthenticator.java | 148 + .../cloudstack/ldap/LdapConfiguration.java | 202 + .../cloudstack/ldap/LdapConfigurationVO.java | 66 + .../cloudstack/ldap/LdapContextFactory.java | 115 + .../apache/cloudstack/ldap/LdapManager.java | 61 + .../cloudstack/ldap/LdapManagerImpl.java | 284 + .../cloudstack/ldap/LdapTrustMapVO.java | 115 + .../org/apache/cloudstack/ldap/LdapUser.java | 88 + .../cloudstack/ldap/LdapUserManager.java | 46 + .../ldap/LdapUserManagerFactory.java | 65 + .../org/apache/cloudstack/ldap/LdapUtils.java | 61 + .../NoLdapUserMatchingQueryException.java | 32 + .../ldap/NoSuchLdapUserException.java | 31 + .../ldap/OpenLdapUserManagerImpl.java | 304 + .../ldap/dao/LdapConfigurationDao.java | 30 + .../ldap/dao/LdapConfigurationDaoImpl.java | 62 + .../cloudstack/ldap/dao/LdapTrustMapDao.java | 27 + .../ldap/dao/LdapTrustMapDaoImpl.java | 45 + .../cloudstack/ldap/module.properties | 18 + .../cloudstack/ldap/spring-ldap-context.xml | 40 + .../ldap/ADLdapUserManagerImplSpec.groovy | 85 + .../ldap/BasicNamingEnumerationImpl.groovy | 56 + .../ldap/LdapAddConfigurationCmdSpec.groovy | 89 + .../ldap/LdapAuthenticatorSpec.groovy | 249 + .../ldap/LdapConfigurationDaoImplSpec.groovy | 29 + .../ldap/LdapConfigurationResponseSpec.groovy | 49 + .../ldap/LdapConfigurationSpec.groovy | 309 + .../ldap/LdapConfigurationVOSpec.groovy | 36 + .../ldap/LdapContextFactorySpec.groovy | 120 + .../ldap/LdapCreateAccountCmdSpec.groovy | 167 + .../LdapDeleteConfigurationCmdSpec.groovy | 68 + .../ldap/LdapImportUsersCmdSpec.groovy | 290 + .../ldap/LdapListConfigurationCmdSpec.groovy | 98 + .../ldap/LdapListUsersCmdSpec.groovy | 121 + .../ldap/LdapManagerImplSpec.groovy | 585 + .../ldap/LdapSearchUserCmdSpec.groovy | 71 + .../ldap/LdapUserManagerFactorySpec.groovy | 57 + .../ldap/LdapUserResponseSpec.groovy | 85 + .../cloudstack/ldap/LdapUserSpec.groovy | 110 + .../cloudstack/ldap/LdapUtilsSpec.groovy | 68 + .../ldap/LinkDomainToLdapCmdSpec.groovy | 232 + ...oLdapUserMatchingQueryExceptionSpec.groovy | 30 + .../ldap/NoSuchLdapUserExceptionSpec.groovy | 30 + .../ldap/OpenLdapUserManagerSpec.groovy | 337 + .../ldap/DistinguishedNameParserTest.java | 61 + .../src/test/resources/cosmic.cloud.ldif | 311 + .../README.md | 1 + .../pom.xml | 65 + .../auth/SHA256SaltedUserAuthenticator.java | 143 + .../cloudstack/sha256salted/module.properties | 18 + .../spring-sha256salted-context.xml | 34 + .../server/auth/test/AuthenticatorTest.java | 123 + pom.xml | 691 + 5430 files changed, 1061504 insertions(+), 2 deletions(-) create mode 100644 build/replace.properties create mode 100644 cosmic-agent/LICENSE create mode 100644 cosmic-agent/README.md create mode 100755 cosmic-agent/bindir/cloud-setup-agent.in create mode 100644 cosmic-agent/bindir/cloud-ssh.in create mode 100644 cosmic-agent/bindir/cosmic-agent-profile.sh.in create mode 100644 cosmic-agent/bindir/cosmic-agent-upgrade.in create mode 100755 cosmic-agent/bindir/libvirtqemuhook.in create mode 100644 cosmic-agent/conf/agent.properties create mode 100644 cosmic-agent/conf/cosmic-agent.logrotate create mode 100644 cosmic-agent/conf/developer.properties.template create mode 100644 cosmic-agent/conf/environment.properties.in create mode 100644 cosmic-agent/conf/log4j-cloud.xml.in create mode 100644 cosmic-agent/pom.xml create mode 100644 cosmic-agent/src/main/java/com/cloud/agent/Agent.java create mode 100644 cosmic-agent/src/main/java/com/cloud/agent/AgentShell.java create mode 100644 cosmic-agent/src/main/java/com/cloud/agent/IAgentShell.java create mode 100644 cosmic-agent/src/main/java/com/cloud/agent/dao/StorageComponent.java create mode 100644 cosmic-agent/src/main/java/com/cloud/agent/dao/impl/PropertiesStorage.java create mode 100644 cosmic-agent/src/main/java/com/cloud/agent/resource/consoleproxy/ConsoleProxyAuthenticationResult.java create mode 100644 cosmic-agent/src/main/java/com/cloud/agent/resource/consoleproxy/ConsoleProxyResource.java create mode 100644 cosmic-agent/src/test/java/com/cloud/agent/AgentShellTest.java create mode 100644 cosmic-agent/src/test/java/com/cloud/agent/dao/impl/PropertiesStorageTest.java create mode 100644 cosmic-client/LICENSE create mode 100644 cosmic-client/README.md create mode 100644 cosmic-client/WEB-INF/classes/resources/messages.properties create mode 100644 cosmic-client/WEB-INF/web.xml create mode 100755 cosmic-client/bindir/cloud-setup-management.in create mode 100755 cosmic-client/bindir/cloud-update-xenserver-licenses.in create mode 100644 cosmic-client/cosmic-ui/LICENSE create mode 100644 cosmic-client/cosmic-ui/README.md create mode 100644 cosmic-client/cosmic-ui/css/cloudstack3-ie7.css create mode 100644 cosmic-client/cosmic-ui/css/cloudstack3.css create mode 100644 cosmic-client/cosmic-ui/css/cloudstack3.hu.css create mode 100644 cosmic-client/cosmic-ui/css/cloudstack3.ja_JP.css create mode 100644 cosmic-client/cosmic-ui/css/custom.css create mode 100644 cosmic-client/cosmic-ui/css/token-input-facebook.css create mode 100644 cosmic-client/cosmic-ui/dictionary.jsp create mode 100644 cosmic-client/cosmic-ui/dictionary2.jsp create mode 100644 cosmic-client/cosmic-ui/error.jsp create mode 100644 cosmic-client/cosmic-ui/images/ajax-loader-small.gif create mode 100644 cosmic-client/cosmic-ui/images/ajax-loader.gif create mode 100644 cosmic-client/cosmic-ui/images/bg-breadcrumb-project-view.png create mode 100644 cosmic-client/cosmic-ui/images/bg-breadcrumb.png create mode 100644 cosmic-client/cosmic-ui/images/bg-breadcrumbs-project-view.png create mode 100644 cosmic-client/cosmic-ui/images/bg-breadcrumbs.png create mode 100644 cosmic-client/cosmic-ui/images/bg-button-view-more.png create mode 100644 cosmic-client/cosmic-ui/images/bg-details-tab-gradient.png create mode 100644 cosmic-client/cosmic-ui/images/bg-dialog-body.png create mode 100644 cosmic-client/cosmic-ui/images/bg-dialog-header.png create mode 100644 cosmic-client/cosmic-ui/images/bg-gradient-white-transparent.png create mode 100644 cosmic-client/cosmic-ui/images/bg-gradients.png create mode 100644 cosmic-client/cosmic-ui/images/bg-header.png create mode 100644 cosmic-client/cosmic-ui/images/bg-install-wizard-header.jpg create mode 100644 cosmic-client/cosmic-ui/images/bg-install-wizard-header.png create mode 100644 cosmic-client/cosmic-ui/images/bg-login.jpg create mode 100644 cosmic-client/cosmic-ui/images/bg-login.png create mode 100644 cosmic-client/cosmic-ui/images/bg-naas.png create mode 100644 cosmic-client/cosmic-ui/images/bg-nav-item-active-project-view.png create mode 100644 cosmic-client/cosmic-ui/images/bg-nav-item-active.png create mode 100644 cosmic-client/cosmic-ui/images/bg-nav-item-project-view.png create mode 100644 cosmic-client/cosmic-ui/images/bg-nav-item.png create mode 100644 cosmic-client/cosmic-ui/images/bg-network-nat.png create mode 100644 cosmic-client/cosmic-ui/images/bg-network.png create mode 100644 cosmic-client/cosmic-ui/images/bg-notifications.png create mode 100644 cosmic-client/cosmic-ui/images/bg-panel-shadow.png create mode 100644 cosmic-client/cosmic-ui/images/bg-section-switcher.png create mode 100644 cosmic-client/cosmic-ui/images/bg-status_box.png create mode 100644 cosmic-client/cosmic-ui/images/bg-system-chart-compute.png create mode 100644 cosmic-client/cosmic-ui/images/bg-system-chart-lines.png create mode 100644 cosmic-client/cosmic-ui/images/bg-system-network-traffic.png create mode 100644 cosmic-client/cosmic-ui/images/bg-table-head.png create mode 100644 cosmic-client/cosmic-ui/images/bg-transparent-white.png create mode 100644 cosmic-client/cosmic-ui/images/bg-what-is-cloudstack.png create mode 100644 cosmic-client/cosmic-ui/images/buttons.png create mode 100644 cosmic-client/cosmic-ui/images/cosmic.ico create mode 100644 cosmic-client/cosmic-ui/images/destroy-anim.gif create mode 100644 cosmic-client/cosmic-ui/images/gradients.png create mode 100644 cosmic-client/cosmic-ui/images/header-gradient.png create mode 100644 cosmic-client/cosmic-ui/images/icons.png create mode 100644 cosmic-client/cosmic-ui/images/infrastructure-icons.png create mode 100644 cosmic-client/cosmic-ui/images/install-wizard-parts.png create mode 100644 cosmic-client/cosmic-ui/images/instance-wizard-parts.png create mode 100644 cosmic-client/cosmic-ui/images/logo-cosmic.png create mode 100644 cosmic-client/cosmic-ui/images/minus.png create mode 100644 cosmic-client/cosmic-ui/images/overlay-pattern.png create mode 100644 cosmic-client/cosmic-ui/images/sky.jpg create mode 100755 cosmic-client/cosmic-ui/images/sprites.png create mode 100644 cosmic-client/cosmic-ui/index.jsp create mode 100644 cosmic-client/cosmic-ui/lib/date.js create mode 100644 cosmic-client/cosmic-ui/lib/excanvas.js create mode 100644 cosmic-client/cosmic-ui/lib/flot/jquery.colorhelpers.js create mode 100644 cosmic-client/cosmic-ui/lib/flot/jquery.flot.crosshair.js create mode 100644 cosmic-client/cosmic-ui/lib/flot/jquery.flot.fillbetween.js create mode 100644 cosmic-client/cosmic-ui/lib/flot/jquery.flot.image.js create mode 100644 cosmic-client/cosmic-ui/lib/flot/jquery.flot.js create mode 100644 cosmic-client/cosmic-ui/lib/flot/jquery.flot.navigate.js create mode 100644 cosmic-client/cosmic-ui/lib/flot/jquery.flot.pie.js create mode 100644 cosmic-client/cosmic-ui/lib/flot/jquery.flot.resize.js create mode 100644 cosmic-client/cosmic-ui/lib/flot/jquery.flot.selection.js create mode 100644 cosmic-client/cosmic-ui/lib/flot/jquery.flot.stack.js create mode 100644 cosmic-client/cosmic-ui/lib/flot/jquery.flot.symbol.js create mode 100644 cosmic-client/cosmic-ui/lib/flot/jquery.flot.threshold.js create mode 100755 cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-bg_flat_0_aaaaaa_40x100.png create mode 100755 cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-bg_flat_75_ffffff_40x100.png create mode 100755 cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-bg_glass_55_fbf9ee_1x400.png create mode 100755 cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-bg_glass_65_ffffff_1x400.png create mode 100755 cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-bg_glass_75_dadada_1x400.png create mode 100755 cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-bg_glass_75_e6e6e6_1x400.png create mode 100755 cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-bg_highlight-hard_75_a7c1d2_1x100.png create mode 100755 cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-bg_inset-soft_95_fef1ec_1x100.png create mode 100755 cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-icons_222222_256x240.png create mode 100755 cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-icons_2e83ff_256x240.png create mode 100755 cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-icons_454545_256x240.png create mode 100755 cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-icons_888888_256x240.png create mode 100755 cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-icons_cd0a0a_256x240.png create mode 100755 cosmic-client/cosmic-ui/lib/jquery-ui/css/jquery-ui.css create mode 100755 cosmic-client/cosmic-ui/lib/jquery-ui/index.html create mode 100755 cosmic-client/cosmic-ui/lib/jquery-ui/js/jquery-ui.js create mode 100644 cosmic-client/cosmic-ui/lib/jquery.cookies.js create mode 100644 cosmic-client/cosmic-ui/lib/jquery.easing.js create mode 100644 cosmic-client/cosmic-ui/lib/jquery.js create mode 100644 cosmic-client/cosmic-ui/lib/jquery.md5.js create mode 100644 cosmic-client/cosmic-ui/lib/jquery.tokeninput.js create mode 100644 cosmic-client/cosmic-ui/lib/jquery.validate.additional-methods.js create mode 100644 cosmic-client/cosmic-ui/lib/jquery.validate.js create mode 100644 cosmic-client/cosmic-ui/lib/qunit/qunit.css create mode 100644 cosmic-client/cosmic-ui/lib/qunit/qunit.js create mode 100644 cosmic-client/cosmic-ui/lib/require.js create mode 100644 cosmic-client/cosmic-ui/lib/reset.css create mode 100644 cosmic-client/cosmic-ui/modules/infrastructure/infrastructure.css create mode 100644 cosmic-client/cosmic-ui/modules/infrastructure/infrastructure.js create mode 100644 cosmic-client/cosmic-ui/modules/modules.js create mode 100644 cosmic-client/cosmic-ui/modules/vpc/vpc.css create mode 100644 cosmic-client/cosmic-ui/modules/vpc/vpc.js create mode 100644 cosmic-client/cosmic-ui/plugins/plugins.js create mode 100644 cosmic-client/cosmic-ui/scripts/accounts.js create mode 100644 cosmic-client/cosmic-ui/scripts/accountsWizard.js create mode 100644 cosmic-client/cosmic-ui/scripts/affinity.js create mode 100644 cosmic-client/cosmic-ui/scripts/autoscaler.js create mode 100644 cosmic-client/cosmic-ui/scripts/cloud.core.callbacks.js create mode 100644 cosmic-client/cosmic-ui/scripts/cloudStack.js create mode 100644 cosmic-client/cosmic-ui/scripts/configuration.js create mode 100644 cosmic-client/cosmic-ui/scripts/dashboard.js create mode 100755 cosmic-client/cosmic-ui/scripts/docs.js create mode 100644 cosmic-client/cosmic-ui/scripts/domains.js create mode 100644 cosmic-client/cosmic-ui/scripts/events.js create mode 100644 cosmic-client/cosmic-ui/scripts/globalSettings.js create mode 100644 cosmic-client/cosmic-ui/scripts/installWizard.js create mode 100644 cosmic-client/cosmic-ui/scripts/instanceWizard.js create mode 100644 cosmic-client/cosmic-ui/scripts/instances.js create mode 100644 cosmic-client/cosmic-ui/scripts/lbStickyPolicy.js create mode 100644 cosmic-client/cosmic-ui/scripts/metrics.js create mode 100755 cosmic-client/cosmic-ui/scripts/network.js create mode 100644 cosmic-client/cosmic-ui/scripts/plugins.js create mode 100644 cosmic-client/cosmic-ui/scripts/projects.js create mode 100644 cosmic-client/cosmic-ui/scripts/regions.js create mode 100644 cosmic-client/cosmic-ui/scripts/sharedFunctions.js create mode 100644 cosmic-client/cosmic-ui/scripts/storage.js create mode 100644 cosmic-client/cosmic-ui/scripts/system.js create mode 100644 cosmic-client/cosmic-ui/scripts/templates.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui-custom/accountsWizard.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui-custom/affinity.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui-custom/autoscaler.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui-custom/dashboard.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui-custom/enableStaticNAT.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui-custom/granularSettings.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui-custom/healthCheck.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui-custom/installWizard.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui-custom/instanceWizard.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui-custom/ipRules.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui-custom/login.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui-custom/metricsView.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui-custom/physicalResources.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui-custom/pluginListing.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui-custom/projectSelect.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui-custom/projects.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui-custom/recurringSnapshots.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui-custom/regions.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui-custom/saml.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui-custom/securityRules.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui-custom/uploadVolume.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui-custom/vpc.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui-custom/zoneChart.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui-custom/zoneFilter.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui-custom/zoneWizard.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui/core.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui/dialog.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui/events.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui/utils.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui/widgets/cloudBrowser.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui/widgets/dataTable.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui/widgets/detailView.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui/widgets/listView.js create mode 100755 cosmic-client/cosmic-ui/scripts/ui/widgets/multiEdit.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui/widgets/notifications.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui/widgets/overlay.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui/widgets/tagger.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui/widgets/toolTip.js create mode 100644 cosmic-client/cosmic-ui/scripts/ui/widgets/treeView.js create mode 100644 cosmic-client/cosmic-ui/scripts/vm_snapshots.js create mode 100644 cosmic-client/cosmic-ui/scripts/vpc.js create mode 100755 cosmic-client/cosmic-ui/scripts/zoneWizard.js create mode 100755 cosmic-client/distro/centos/SYSCONFDIR/rc.d/init.d/cloud-management.in create mode 100644 cosmic-client/distro/centos/SYSCONFDIR/sysconfig/cloud-management.in create mode 100755 cosmic-client/distro/fedora/SYSCONFDIR/rc.d/init.d/cloud-management.in create mode 100644 cosmic-client/distro/fedora/SYSCONFDIR/sysconfig/cloud-management.in create mode 100755 cosmic-client/distro/opensuse/SYSCONFDIR/init.d/cloud-management.in create mode 100644 cosmic-client/distro/opensuse/SYSCONFDIR/sysconfig/cloud-management.in create mode 100644 cosmic-client/distro/rhel/SYSCONFDIR/rc.d/init.d/cloud-management.in create mode 100644 cosmic-client/distro/rhel/SYSCONFDIR/sysconfig/cloud-management.in create mode 100755 cosmic-client/distro/sles/SYSCONFDIR/init.d/cloud-management.in create mode 100644 cosmic-client/distro/sles/SYSCONFDIR/sysconfig/cloud-management.in create mode 100755 cosmic-client/distro/ubuntu/SYSCONFDIR/init.d/cloud-management.in create mode 100644 cosmic-client/pom.xml create mode 100644 cosmic-client/src/main/resources/META-INF/cosmic/webApplicationContext.xml create mode 100644 cosmic-client/tomcatconf/catalina.policy.in create mode 100644 cosmic-client/tomcatconf/catalina.properties.in create mode 100644 cosmic-client/tomcatconf/classpath.conf.in create mode 100644 cosmic-client/tomcatconf/cloudmanagementserver.keystore create mode 100644 cosmic-client/tomcatconf/commands.properties.in create mode 100644 cosmic-client/tomcatconf/commons-logging.properties.in create mode 100644 cosmic-client/tomcatconf/context.xml.in create mode 100644 cosmic-client/tomcatconf/db.properties.in create mode 100755 cosmic-client/tomcatconf/ehcache.xml.in create mode 100644 cosmic-client/tomcatconf/environment.properties.in create mode 100644 cosmic-client/tomcatconf/java.security.ciphers.in create mode 100755 cosmic-client/tomcatconf/log4j-cloud.xml.in create mode 100644 cosmic-client/tomcatconf/logging.properties.in create mode 100755 cosmic-client/tomcatconf/server-nonssl.xml.in create mode 100755 cosmic-client/tomcatconf/server-ssl.xml.in create mode 100755 cosmic-client/tomcatconf/server7-nonssl.xml.in create mode 100755 cosmic-client/tomcatconf/server7-ssl.xml.in create mode 100644 cosmic-client/tomcatconf/tomcat-users.xml.in create mode 100644 cosmic-client/tomcatconf/tomcat6-nonssl.conf.in create mode 100644 cosmic-client/tomcatconf/tomcat6-ssl.conf.in create mode 100644 cosmic-client/tomcatconf/web.xml.in create mode 100644 cosmic-core/LICENSE create mode 100644 cosmic-core/NOTICE create mode 100644 cosmic-core/README.md create mode 100644 cosmic-core/api/pom.xml create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/Answer.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/Command.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/HostVmStateReportEntry.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/LogLevel.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/PvlanSetupCommand.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/StoragePoolInfo.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/UnsupportedAnswer.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/VgpuTypesInfo.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/storage/CopyTemplateToPrimaryStorageAnswer.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/storage/CreateVolumeOVAAnswer.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/storage/CreateVolumeOVACommand.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/storage/PasswordAuth.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/storage/PrepareOVAPackingAnswer.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/storage/PrepareOVAPackingCommand.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/to/DataObjectType.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/to/DataStoreTO.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/to/DataTO.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/to/DhcpTO.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/to/DiskTO.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/to/FirewallRuleTO.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/to/GPUDeviceTO.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/to/HostTO.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/to/IpAddressTO.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/to/LoadBalancerTO.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/to/MonitorServiceTO.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/to/NetworkACLTO.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/to/NetworkTO.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/to/NfsTO.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/to/NicTO.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/to/PortForwardingRuleTO.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/to/S3TO.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/to/StaticNatRuleTO.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/to/StorageFilerTO.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/to/SwiftTO.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/to/TemplateTO.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/to/VirtualMachineTO.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/api/to/VolumeTO.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/manager/allocator/HostAllocator.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/agent/manager/allocator/PodAllocator.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/alert/Alert.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/alert/AlertAdapter.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/api/commands/ListRecurringSnapshotScheduleCmd.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/capacity/Capacity.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/capacity/CapacityState.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/configuration/ConfigurationService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/configuration/Resource.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/configuration/ResourceCount.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/configuration/ResourceLimit.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/consoleproxy/ConsoleProxyAllocator.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/dc/DataCenter.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/dc/DedicatedResources.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/dc/Pod.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/dc/StorageNetworkIpRange.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/dc/Vlan.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/deploy/DataCenterDeployment.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/deploy/DeployDestination.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/deploy/DeploymentClusterPlanner.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/deploy/DeploymentPlan.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/deploy/DeploymentPlanner.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/deploy/HAPlanner.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/domain/Domain.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/domain/PartOf.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/event/ActionEvent.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/event/ActionEvents.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/event/Event.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/event/EventCategory.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/event/EventTypes.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/event/UsageEvent.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/AccountLimitException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/AffinityConflictException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/AgentControlChannelException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/AgentUnavailableException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/CloudAuthenticationException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/CloudException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/ConcurrentOperationException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/ConflictingNetworkSettingsException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/ConnectionException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/DiscoveredWithErrorException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/DiscoveryException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/HAStateException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/IllegalVirtualMachineException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/InsufficientAddressCapacityException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/InsufficientCapacityException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/InsufficientNetworkCapacityException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/InsufficientServerCapacityException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/InsufficientStorageCapacityException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/InsufficientVirtualNetworkCapacityException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/InternalErrorException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/ManagementServerException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/MissingParameterValueException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/NetworkRuleConflictException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/NicPreparationException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/OperationTimedoutException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/PermissionDeniedException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/RequestLimitException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/ResourceAllocationException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/ResourceInUseException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/ResourceUnavailableException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/StorageConflictException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/StorageUnavailableException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/UnsupportedServiceException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/exception/VirtualMachineMigrationException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/gpu/GPU.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/ha/FenceBuilder.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/ha/Investigator.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/host/Host.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/host/HostEnvironment.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/host/HostStats.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/host/Status.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/hypervisor/Hypervisor.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/hypervisor/HypervisorCapabilities.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/info/ConsoleProxyLoadInfo.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/info/RunningHostCountInfo.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/GuestVlan.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/IpAddress.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/MonitoringService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/Network.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/NetworkMigrationResponder.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/NetworkModel.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/NetworkProfile.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/NetworkRuleApplier.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/NetworkService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/NetworkUsageService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/Networks.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/PhysicalNetwork.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/PhysicalNetworkServiceProvider.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/PhysicalNetworkSetupInfo.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/PhysicalNetworkTrafficType.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/PublicIpAddress.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/RemoteAccessVpn.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/Site2SiteCustomerGateway.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/Site2SiteVpnConnection.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/Site2SiteVpnGateway.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/StorageNetworkService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/TrafficLabel.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/UserIpv6Address.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/VirtualNetworkApplianceService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/VirtualRouterProvider.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/VpcVirtualNetworkApplianceService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/VpnUser.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/as/AutoScaleCounter.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/as/AutoScalePolicy.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/as/AutoScaleService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/as/AutoScaleVmGroup.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/as/AutoScaleVmProfile.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/as/Condition.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/as/Counter.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/element/AggregatedCommandExecutor.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/element/ConnectivityProvider.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/element/DhcpServiceProvider.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/element/FirewallServiceProvider.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/element/IpDeployer.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/element/IpDeployingRequester.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/element/LoadBalancingServiceProvider.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/element/NetworkACLServiceProvider.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/element/NetworkElement.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/element/PortForwardingServiceProvider.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/element/RemoteAccessVPNServiceProvider.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/element/Site2SiteVpnServiceProvider.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/element/SourceNatServiceProvider.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/element/StaticNatServiceProvider.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/element/UserDataServiceProvider.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/element/VirtualRouterElementService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/element/VpcProvider.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/firewall/FirewallService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/guru/NetworkGuru.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/lb/CertService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/lb/LoadBalancingRule.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/lb/LoadBalancingRulesService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/lb/SslCert.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/router/VirtualRouter.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/rules/FirewallRule.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/rules/HealthCheckPolicy.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/rules/LbStickinessMethod.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/rules/LoadBalancer.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/rules/LoadBalancerContainer.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/rules/PortForwardingRule.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/rules/RulesService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/rules/StaticNat.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/rules/StaticNatRule.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/rules/StickinessPolicy.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/security/SecurityGroup.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/security/SecurityGroupRules.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/security/SecurityGroupService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/security/SecurityRule.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/vpc/NetworkACL.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/vpc/NetworkACLItem.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/vpc/NetworkACLService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/vpc/PrivateGateway.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/vpc/PrivateIp.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/vpc/StaticRoute.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/vpc/StaticRouteProfile.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/vpc/Vpc.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/vpc/VpcGateway.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/vpc/VpcOffering.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/vpc/VpcProvisioningService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/vpc/VpcService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/vpn/RemoteAccessVpnService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/network/vpn/Site2SiteVpnService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/offering/DiskOffering.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/offering/DiskOfferingInfo.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/offering/NetworkOffering.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/offering/OfferingManager.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/offering/ServiceOffering.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/org/Cluster.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/org/Grouping.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/org/Managed.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/org/RunningIn.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/projects/Project.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/projects/ProjectAccount.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/projects/ProjectInvitation.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/projects/ProjectService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/region/ha/GlobalLoadBalancerRule.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/region/ha/GlobalLoadBalancingRulesService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/resource/ResourceService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/resource/ResourceState.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/resource/UnableDeleteHostException.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/serializer/Param.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/server/ManagementService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/server/ResourceMetaDataService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/server/ResourceTag.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/server/TaggedResourceService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/storage/DataStoreProviderApiService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/storage/DataStoreRole.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/storage/GuestOS.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/storage/GuestOSHypervisor.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/storage/GuestOsCategory.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/storage/ImageStore.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/storage/ScopeType.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/storage/Snapshot.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/storage/Storage.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/storage/StorageGuru.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/storage/StoragePool.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/storage/StoragePoolDiscoverer.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/storage/StoragePoolStatus.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/storage/StorageService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/storage/StorageStats.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/storage/Upload.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/storage/VMTemplateStorageResourceAssoc.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/storage/Volume.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/storage/VolumeApiService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/storage/VolumeStats.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/storage/snapshot/SnapshotApiService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/storage/snapshot/SnapshotPolicy.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/storage/snapshot/SnapshotSchedule.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/storage/template/TemplateProp.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/template/BasedOn.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/template/TemplateApiService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/template/VirtualMachineTemplate.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/user/Account.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/user/AccountService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/user/DomainService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/user/OwnedBy.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/user/ResourceLimitService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/user/SSHKeyPair.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/user/User.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/user/UserAccount.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/uservm/UserVm.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/vm/ConsoleProxy.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/vm/DiskProfile.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/vm/InstanceGroup.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/vm/Nic.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/vm/NicIpAlias.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/vm/NicProfile.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/vm/NicSecondaryIp.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/vm/ReservationContext.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/vm/RunningOn.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/vm/SecondaryStorageVm.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/vm/SystemVm.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/vm/UserVmService.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/vm/VirtualMachine.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/vm/VirtualMachineName.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/vm/VirtualMachineProfile.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/vm/VmDetailConstants.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/vm/VmDiskStats.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/vm/VmStats.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/vm/snapshot/VMSnapshot.java create mode 100644 cosmic-core/api/src/main/java/com/cloud/vm/snapshot/VMSnapshotService.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/acl/APIChecker.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/acl/APILimitChecker.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/acl/ControlledEntity.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/acl/InfrastructureEntity.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/acl/PermissionScope.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/acl/QuerySelector.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/acl/RoleType.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/acl/SecurityChecker.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/affinity/AffinityGroup.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/affinity/AffinityGroupProcessor.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/affinity/AffinityGroupResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/affinity/AffinityGroupService.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/affinity/AffinityGroupTypeResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/affinity/AffinityProcessorBase.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/alert/AlertService.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/ACL.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/APICommand.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/AbstractGetUploadParamsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/ApiCommandJobType.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/ApiErrorCode.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/ApiServerService.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/BaseAsyncCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/BaseAsyncCreateCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/BaseAsyncCreateCustomIdCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/BaseAsyncCustomIdCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/BaseCustomIdCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/BaseListAccountResourcesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/BaseListCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/BaseListDomainResourcesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/BaseListProjectAndAccountResourcesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/BaseListTaggedResourcesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/BaseListTemplateOrIsoPermissionsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/BaseResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoPermissionsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/Displayable.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/EntityReference.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/IBaseListAccountResourcesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/IBaseListCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/IBaseListDomainResourcesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/IBaseListProjectAndAccountResourcesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/IBaseListTaggedResourcesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/Identity.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/InternalIdentity.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/LdapValidator.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/Parameter.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/ResourceDetail.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/ResponseObject.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/ServerApiException.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/Validate.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/auth/APIAuthenticationManager.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/auth/APIAuthenticationType.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/auth/APIAuthenticator.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/auth/PluggableAPIAuthenticator.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/account/CreateAccountCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/account/DeleteAccountCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/account/DisableAccountCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/account/EnableAccountCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/account/ListAccountsCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/account/LockAccountCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/account/UpdateAccountCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/address/AssociateIPAddrCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/address/ListPublicIpAddressesCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/affinitygroup/UpdateVMAffinityGroupCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/alert/GenerateAlertCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/autoscale/CreateCounterCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/autoscale/DeleteCounterCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/AddClusterCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/DeleteClusterCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ListClustersCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/UpdateClusterCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/config/ListCfgsByCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/config/ListDeploymentPlannersCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/config/ListHypervisorCapabilitiesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/config/UpdateCfgCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/config/UpdateHypervisorCapabilitiesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/CreateDomainCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/DeleteDomainCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/ListDomainChildrenCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/ListDomainsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/ListDomainsCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/UpdateDomainCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/AddGuestOsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/AddGuestOsMappingCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/ListGuestOsMappingCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/RemoveGuestOsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/RemoveGuestOsMappingCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/UpdateGuestOsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/UpdateGuestOsMappingCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/host/AddHostCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/host/AddSecondaryStorageCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/host/CancelMaintenanceCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/host/DeleteHostCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/host/FindHostsForMigrationCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ListHostTagsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ListHostsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/host/PrepareForMaintenanceCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ReconnectHostCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ReleaseHostReservationCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/host/UpdateHostCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/host/UpdateHostPasswordCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/internallb/ConfigureInternalLoadBalancerElementCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/internallb/CreateInternalLoadBalancerElementCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/internallb/ListInternalLBVMsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/internallb/ListInternalLoadBalancerElementsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/internallb/StartInternalLBVMCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/internallb/StopInternalLBVMCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/iso/AttachIsoCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/iso/CopyIsoCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/iso/DetachIsoCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/iso/ListIsoPermissionsCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/iso/ListIsosCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/iso/RegisterIsoCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/iso/UpdateIsoCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/loadbalancer/ListLoadBalancerRuleInstancesCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/network/AddNetworkDeviceCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/network/AddNetworkServiceProviderCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateNetworkCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateNetworkOfferingCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreatePhysicalNetworkCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateStorageNetworkIpRangeCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DedicateGuestVlanRangeCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteNetworkDeviceCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteNetworkOfferingCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteNetworkServiceProviderCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeletePhysicalNetworkCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteStorageNetworkIpRangeCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ListDedicatedGuestVlanRangesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ListNetworkDeviceCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ListNetworkIsolationMethodsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ListNetworkServiceProvidersCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ListNetworksCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ListPhysicalNetworksCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ListStorageNetworkIpRangeCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ListSupportedNetworkServicesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ReleaseDedicatedGuestVlanRangeCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdateNetworkCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdateNetworkOfferingCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdateNetworkServiceProviderCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdatePhysicalNetworkCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdateStorageNetworkIpRangeCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/DeleteDiskOfferingCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/DeleteServiceOfferingCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateDiskOfferingCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateServiceOfferingCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/pod/CreatePodCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/pod/DeletePodCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/pod/ListPodsByCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/pod/UpdatePodCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/region/AddRegionCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/region/CreatePortableIpRangeCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/region/DeletePortableIpRangeCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/region/ListPortableIpRangesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/region/RemoveRegionCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/region/UpdateRegionCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/ArchiveAlertsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/CleanVMReservationsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/DeleteAlertsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/ListAlertsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/ListCapacityCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/UploadCustomCertificateCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ConfigureVirtualRouterElementCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/router/CreateVirtualRouterElementCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/router/DestroyRouterCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ListRoutersCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ListVirtualRouterElementsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/router/RebootRouterCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/router/StartRouterCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/router/StopRouterCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/router/UpgradeRouterCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/router/UpgradeRouterTemplateCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/AddImageStoreCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/AddImageStoreS3CMD.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/CancelPrimaryStorageMaintenanceCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/CreateSecondaryStagingStoreCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/CreateStoragePoolCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/DeleteImageStoreCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/DeletePoolCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/DeleteSecondaryStagingStoreCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/FindStoragePoolsForMigrationCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListImageStoresCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListSecondaryStagingStoresCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListStoragePoolsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListStorageProvidersCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListStorageTagsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/PreparePrimaryStorageForMaintenanceCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/UpdateCloudToUseObjectStoreCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/UpdateStoragePoolCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/swift/AddSwiftCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/swift/ListSwiftsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/DestroySystemVmCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/ListSystemVMsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/MigrateSystemVMCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/RebootSystemVmCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/ScaleSystemVMCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/StartSystemVMCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/StopSystemVmCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/UpgradeSystemVMCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/template/CopyTemplateCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/template/CreateTemplateCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/template/ListTemplatePermissionsCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/template/ListTemplatesCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/template/PrepareTemplateCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/template/RegisterTemplateCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/template/UpdateTemplateCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/AddTrafficMonitorCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/AddTrafficTypeCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/DeleteTrafficMonitorCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/DeleteTrafficTypeCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/GenerateUsageRecordsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/GetUsageRecordsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/ListTrafficMonitorsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/ListTrafficTypeImplementorsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/ListTrafficTypesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/ListUsageTypesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/RemoveRawUsageRecordsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/UpdateTrafficTypeCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/user/CreateUserCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/user/DeleteUserCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/user/DisableUserCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/user/EnableUserCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/user/GetUserCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUsersCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/user/LockUserCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/user/RegisterCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/user/UpdateUserCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vlan/CreateVlanIpRangeCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vlan/DedicatePublicIpRangeCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vlan/DeleteVlanIpRangeCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vlan/ListVlanIpRangesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vlan/ReleasePublicIpRangeCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/AddNicToVMCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/AssignVMCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/DeployVMCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/DestroyVMCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ExpungeVMCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/GetVMUserDataCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListVMsCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/MigrateVMCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/MigrateVirtualMachineWithVolumeCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/RebootVMCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/RecoverVMCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/RemoveNicFromVMCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ResetVMPasswordCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ResetVMSSHKeyCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/RestoreVMCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ScaleVMCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/StartVMCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/StopVMCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/UpdateDefaultNicForVMCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/UpdateVMCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/UpgradeVMCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vmsnapshot/RevertToVMSnapshotCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/AttachVolumeCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/CreateVolumeCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/DetachVolumeCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/ListVolumesCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/MigrateVolumeCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/ResizeVolumeCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/UpdateVolumeCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/UploadVolumeCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreatePrivateGatewayCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/DeletePrivateGatewayCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/DeleteVPCOfferingCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/ListVPCsCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/UpdateVPCCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/UpdateVPCOfferingCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/CreateZoneCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/DeleteZoneCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/ListZonesCmdByAdmin.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/MarkDefaultZoneForAccountCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/UpdateZoneCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddAccountToProjectCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/account/DeleteAccountFromProjectCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/account/ListAccountsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/account/ListProjectAccountsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/address/AssociateIPAddrCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/address/DisassociateIPAddrCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/address/ListPublicIpAddressesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/address/UpdateIPAddrCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/affinitygroup/CreateAffinityGroupCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/affinitygroup/DeleteAffinityGroupCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/affinitygroup/ListAffinityGroupTypesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/affinitygroup/ListAffinityGroupsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/affinitygroup/UpdateVMAffinityGroupCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateAutoScalePolicyCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateAutoScaleVmGroupCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateAutoScaleVmProfileCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateConditionCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/DeleteAutoScalePolicyCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/DeleteAutoScaleVmGroupCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/DeleteAutoScaleVmProfileCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/DeleteConditionCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/DisableAutoScaleVmGroupCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/EnableAutoScaleVmGroupCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/ListAutoScalePoliciesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/ListAutoScaleVmGroupsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/ListAutoScaleVmProfilesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/ListConditionsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/ListCountersCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateAutoScalePolicyCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateAutoScaleVmGroupCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateAutoScaleVmProfileCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/event/ArchiveEventsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/event/DeleteEventsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/event/ListEventTypesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/event/ListEventsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateEgressFirewallRuleCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateFirewallRuleCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/DeleteEgressFirewallRuleCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/DeleteFirewallRuleCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/DeletePortForwardingRuleCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/IListFirewallRulesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/ListEgressFirewallRulesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/ListFirewallRulesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/ListPortForwardingRulesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/UpdateEgressFirewallRuleCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/UpdateFirewallRuleCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/UpdatePortForwardingRuleCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/guest/ListGuestOsCategoriesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/guest/ListGuestOsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/iso/AttachIsoCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/iso/CopyIsoCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/iso/DeleteIsoCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/iso/DetachIsoCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ExtractIsoCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ListIsoPermissionsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ListIsosCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/iso/RegisterIsoCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/iso/UpdateIsoCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/iso/UpdateIsoPermissionsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/job/ListAsyncJobsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/job/QueryAsyncJobResultCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/AssignCertToLoadBalancerCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/AssignToLoadBalancerRuleCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateApplicationLoadBalancerCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateLBHealthCheckPolicyCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateLBStickinessPolicyCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateLoadBalancerRuleCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/DeleteApplicationLoadBalancerCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/DeleteLBHealthCheckPolicyCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/DeleteLBStickinessPolicyCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/DeleteLoadBalancerRuleCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/DeleteSslCertCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/ListApplicationLoadBalancersCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/ListLBHealthCheckPoliciesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/ListLBStickinessPoliciesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/ListLoadBalancerRuleInstancesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/ListLoadBalancerRulesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/ListSslCertsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/RemoveCertFromLoadBalancerCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/RemoveFromLoadBalancerRuleCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateApplicationLoadBalancerCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateLBHealthCheckPolicyCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateLBStickinessPolicyCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateLoadBalancerRuleCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UploadSslCertCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/nat/CreateIpForwardingRuleCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/nat/DeleteIpForwardingRuleCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/nat/DisableStaticNatCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/nat/EnableStaticNatCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/nat/ListIpForwardingRulesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkACLCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkACLListCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/network/DeleteNetworkACLCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/network/DeleteNetworkACLListCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/network/DeleteNetworkCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/network/ListNetworkACLListsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/network/ListNetworkACLsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/network/ListNetworkOfferingsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/network/ListNetworksCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/network/ReplaceNetworkACLListCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/network/RestartNetworkCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/network/UpdateNetworkACLItemCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/network/UpdateNetworkACLListCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/network/UpdateNetworkCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListDiskOfferingsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListServiceOfferingsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/project/ActivateProjectCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/project/CreateProjectCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/project/DeleteProjectCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/project/DeleteProjectInvitationCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/project/ListProjectInvitationsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/project/ListProjectsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/project/SuspendProjectCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectInvitationCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/region/ListRegionsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/AssignToGlobalLoadBalancerRuleCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/CreateGlobalLoadBalancerRuleCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/DeleteGlobalLoadBalancerRuleCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/ListGlobalLoadBalancerRuleCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/RemoveFromGlobalLoadBalancerRuleCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/UpdateGlobalLoadBalancerRuleCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/resource/GetCloudIdentifierCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/resource/ListHypervisorsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/resource/ListResourceLimitsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/resource/UpdateResourceCountCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/resource/UpdateResourceLimitCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/securitygroup/AuthorizeSecurityGroupEgressCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/securitygroup/AuthorizeSecurityGroupIngressCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/securitygroup/CreateSecurityGroupCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/securitygroup/DeleteSecurityGroupCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/securitygroup/ListSecurityGroupsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/securitygroup/RevokeSecurityGroupEgressCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/securitygroup/RevokeSecurityGroupIngressCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotPolicyCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/DeleteSnapshotCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/DeleteSnapshotPoliciesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ListSnapshotPoliciesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ListSnapshotsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/RevertSnapshotCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/UpdateSnapshotPolicyCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/ssh/CreateSSHKeyPairCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/ssh/DeleteSSHKeyPairCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/ssh/ListSSHKeyPairsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/ssh/RegisterSSHKeyPairCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/tag/CreateTagsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/tag/DeleteTagsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/tag/ListTagsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/template/CopyTemplateCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/template/CreateTemplateCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/template/DeleteTemplateCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/template/ExtractTemplateCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/template/GetUploadParamsForTemplateCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatePermissionsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/template/UpdateTemplateCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/template/UpdateTemplatePermissionsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vm/AddIpToVmNicCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vm/AddNicToVMCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DestroyVMCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vm/GetVMPasswordCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListNicsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RebootVMCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RemoveIpFromVmNicCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RemoveNicFromVMCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ResetVMPasswordCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ResetVMSSHKeyCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RestoreVMCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ScaleVMCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vm/StartVMCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vm/StopVMCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateDefaultNicForVMCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVmNicIpCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpgradeVMCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vmgroup/CreateVMGroupCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vmgroup/DeleteVMGroupCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vmgroup/ListVMGroupsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vmgroup/UpdateVMGroupCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vmsnapshot/CreateVMSnapshotCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vmsnapshot/DeleteVMSnapshotCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vmsnapshot/ListVMSnapshotCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vmsnapshot/RevertToVMSnapshotCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/volume/AddResourceDetailCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/volume/AttachVolumeCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/volume/DeleteVolumeCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/volume/DetachVolumeCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ExtractVolumeCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/volume/GetUploadParamsForVolumeCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ListResourceDetailsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ListVolumesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/volume/MigrateVolumeCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/volume/RemoveResourceDetailCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ResizeVolumeCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/volume/UpdateVolumeCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/volume/UploadVolumeCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateStaticRouteCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateVPCCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/DeleteStaticRouteCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/DeleteVPCCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/ListPrivateGatewaysCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/ListStaticRoutesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/ListVPCOfferingsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/ListVPCsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/RestartVPCCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/UpdateVPCCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/AddVpnUserCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateRemoteAccessVpnCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateVpnConnectionCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateVpnCustomerGatewayCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateVpnGatewayCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/DeleteRemoteAccessVpnCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/DeleteVpnConnectionCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/DeleteVpnCustomerGatewayCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/DeleteVpnGatewayCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/ListRemoteAccessVpnsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/ListVpnConnectionsCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/ListVpnCustomerGatewaysCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/ListVpnGatewaysCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/ListVpnUsersCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/RemoveVpnUserCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/ResetVpnConnectionCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/UpdateRemoteAccessVpnCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/UpdateVpnConnectionCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/UpdateVpnCustomerGatewayCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/UpdateVpnGatewayCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/command/user/zone/ListZonesCmd.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/AccountResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/AddIpToVmNicResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/AlertResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/ApplicationLoadBalancerInstanceResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/ApplicationLoadBalancerResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/ApplicationLoadBalancerRuleResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/AsyncJobResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/AuthenticationCmdResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/AutoScalePolicyResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/AutoScaleVmGroupResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/AutoScaleVmProfileResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/CapabilityResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/CapacityResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/CloudIdentifierResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/ClusterResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/ConditionResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/ConfigurationResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/ControlledEntityResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/ControlledViewEntityResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/CounterResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/CreateCmdResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/CreateSSHKeyPairResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/CustomCertificateResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/DeploymentPlannersResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/DomainResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/DomainRouterResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/EventResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/EventTypeResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/ExceptionResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/ExternalFirewallResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/ExternalLoadBalancerResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/ExtractResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/FirewallResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/FirewallRuleResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/GetUploadParamsResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/GetVMPasswordResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/GlobalLoadBalancerResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/GpuResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/GuestOSCategoryResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/GuestOSResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/GuestOsMappingResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/GuestVlanRangeResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/HostForMigrationResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/HostResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/HostTagResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/HypervisorCapabilitiesResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/HypervisorResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/IPAddressResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/ImageStoreDetailResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/ImageStoreResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/InstanceGroupResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/InternalLoadBalancerElementResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/IpForwardingRuleResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/IsoVmResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/IsolationMethodResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/LBHealthCheckPolicyResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/LBHealthCheckResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/LBStickinessPolicyResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/LBStickinessResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/ListResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/LoadBalancerResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/LoadBalancerRuleVmMapResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/LoginCmdResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/LogoutCmdResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/NetworkACLItemResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/NetworkACLResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/NetworkDeviceResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/NetworkOfferingResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/NicDetailResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/NicResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/NicSecondaryIpResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/PhysicalNetworkResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/PodResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/PortableIpRangeResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/PortableIpResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/PrivateGatewayResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/ProjectAccountResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/ProjectInvitationResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/ProviderResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/RegionResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/RegisterResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/RemoteAccessVpnResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/ResourceCountResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/ResourceDetailResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/ResourceLimitAndCountResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/ResourceLimitResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/ResourceTagResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/SSHKeyPairResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/SecurityGroupResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/SecurityGroupRuleResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/ServiceResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/Site2SiteCustomerGatewayResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/Site2SiteVpnConnectionResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/Site2SiteVpnGatewayResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/SnapshotPolicyResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/SnapshotResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/SnapshotScheduleResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/SslCertResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/StaticRouteResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/StatusResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/StorageNetworkIpRangeResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/StorageProviderResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/StorageTagResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/SuccessResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/SystemVmInstanceResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/SystemVmResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/TemplatePermissionsResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/TemplateZoneResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/TrafficMonitorResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/TrafficTypeImplementorResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/TrafficTypeResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/UpgradeRouterTemplateResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/UpgradeVmResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/UsageRecordResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/UsageTypeResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/UserResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/VMSnapshotResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/VMUserDataResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/VgpuResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/VirtualRouterProviderResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/VlanIpRangeResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/VolumeDetailResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/VolumeResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/VpcOfferingResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/VpnUsersResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/api/response/ZoneResponse.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/config/ApiServiceConfiguration.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/context/CallContext.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/context/CallContextListener.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/context/LogContext.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/context/LogContextListener.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/jobs/JobInfo.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/network/ExternalNetworkDeviceManager.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/network/element/InternalLoadBalancerElementService.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/network/lb/ApplicationLoadBalancerContainer.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/network/lb/ApplicationLoadBalancerRule.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/network/lb/ApplicationLoadBalancerService.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/network/lb/InternalLoadBalancerVMService.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/query/QueryService.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/region/PortableIp.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/region/PortableIpRange.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/region/Region.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/region/RegionService.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/region/RegionSync.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/usage/Usage.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/usage/UsageService.java create mode 100644 cosmic-core/api/src/main/java/org/apache/cloudstack/usage/UsageTypes.java create mode 100644 cosmic-core/api/src/main/resources/META-INF/cloudstack/api-config/module.properties create mode 100644 cosmic-core/api/src/main/resources/META-INF/cloudstack/api-config/spring-api-config-context.xml create mode 100644 cosmic-core/api/src/main/resources/META-INF/cloudstack/api-planner/module.properties create mode 100644 cosmic-core/api/src/main/resources/META-INF/cloudstack/api-planner/spring-api-planner-context.xml create mode 100644 cosmic-core/api/src/test/java/com/cloud/network/NetworksTest.java create mode 100644 cosmic-core/api/src/test/java/integration/api/__init__.py create mode 100644 cosmic-core/api/src/test/java/integration/api/setup.py create mode 100644 cosmic-core/api/src/test/java/integration/api/test/__init__.py create mode 100644 cosmic-core/api/src/test/java/integration/api/test/account/__init__.py create mode 100644 cosmic-core/api/src/test/java/integration/api/test/account/testCreateAccount.py create mode 100644 cosmic-core/api/src/test/java/org/apache/cloudstack/api/ApiCmdTestUtil.java create mode 100644 cosmic-core/api/src/test/java/org/apache/cloudstack/api/BaseCmdTest.java create mode 100644 cosmic-core/api/src/test/java/org/apache/cloudstack/api/command/admin/account/CreateAccountCmdTest.java create mode 100644 cosmic-core/api/src/test/java/org/apache/cloudstack/api/command/admin/storage/CreateSecondaryStagingStoreCmdTest.java create mode 100644 cosmic-core/api/src/test/java/org/apache/cloudstack/api/command/admin/user/CreateUserCmdTest.java create mode 100644 cosmic-core/api/src/test/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmdTest.java create mode 100644 cosmic-core/api/src/test/java/org/apache/cloudstack/api/command/test/ActivateProjectCmdTest.java create mode 100644 cosmic-core/api/src/test/java/org/apache/cloudstack/api/command/test/AddAccountToProjectCmdTest.java create mode 100644 cosmic-core/api/src/test/java/org/apache/cloudstack/api/command/test/AddClusterCmdTest.java create mode 100644 cosmic-core/api/src/test/java/org/apache/cloudstack/api/command/test/AddHostCmdTest.java create mode 100644 cosmic-core/api/src/test/java/org/apache/cloudstack/api/command/test/AddIpToVmNicTest.java create mode 100644 cosmic-core/api/src/test/java/org/apache/cloudstack/api/command/test/AddNetworkServiceProviderCmdTest.java create mode 100644 cosmic-core/api/src/test/java/org/apache/cloudstack/api/command/test/AddSecondaryStorageCmdTest.java create mode 100644 cosmic-core/api/src/test/java/org/apache/cloudstack/api/command/test/AddVpnUserCmdTest.java create mode 100644 cosmic-core/api/src/test/java/org/apache/cloudstack/api/command/test/ListCfgCmdTest.java create mode 100644 cosmic-core/api/src/test/java/org/apache/cloudstack/api/command/test/RegionCmdTest.java create mode 100644 cosmic-core/api/src/test/java/org/apache/cloudstack/api/command/test/ScaleVMCmdTest.java create mode 100644 cosmic-core/api/src/test/java/org/apache/cloudstack/api/command/test/UpdateCfgCmdTest.java create mode 100644 cosmic-core/api/src/test/java/org/apache/cloudstack/api/command/test/UpdateHostPasswordCmdTest.java create mode 100644 cosmic-core/api/src/test/java/org/apache/cloudstack/api/command/test/UpdateVmNicIpTest.java create mode 100644 cosmic-core/api/src/test/java/org/apache/cloudstack/api/command/test/UsageCmdTest.java create mode 100644 cosmic-core/api/src/test/java/org/apache/cloudstack/api/response/HostResponseTest.java create mode 100644 cosmic-core/api/src/test/java/org/apache/cloudstack/context/CallContextTest.java create mode 100644 cosmic-core/api/src/test/java/org/apache/cloudstack/test/utils/SpringUtils.java create mode 100644 cosmic-core/build/replace.properties create mode 100644 cosmic-core/developer/developer-prefill.sql create mode 100644 cosmic-core/developer/pom.xml create mode 100644 cosmic-core/engine/api/pom.xml create mode 100644 cosmic-core/engine/api/src/main/java/com/cloud/vm/VirtualMachineGuru.java create mode 100644 cosmic-core/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/BackupEntity.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/EdgeService.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/NetworkEntity.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/NicEntity.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/SnapshotEntity.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/TemplateEntity.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/VirtualMachineEntity.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/VolumeEntity.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/ClusterEntity.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/DataCenterResourceEntity.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/HostEntity.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/OrganizationScope.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/PodEntity.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/StorageEntity.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/ZoneEntity.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/entity/api/CloudStackEntity.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/exception/InsufficientCapacityException.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/ClusterRestService.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/NetworkRestService.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/PodRestService.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/VirtualMachineRestService.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/VolumeRestService.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/ZoneRestService.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/DirectoryService.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/EntityService.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/OperationsServices.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/OrchestrationService.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/ProvisioningService.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/hypervisor/ComputeSubsystem.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/network/NetworkServiceProvider.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/network/NetworkSubsystem.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/AbstractScope.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/ChapInfo.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/ClusterScope.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/CopyCommandResult.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/CreateCmdResult.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataMotionService.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataMotionStrategy.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataObject.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataObjectInStore.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStore.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreCapabilities.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreDriver.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreLifeCycle.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreManager.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreProvider.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreProviderManager.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/EndPoint.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/EndPointSelector.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/HostScope.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/HypervisorHostListener.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/ImageStoreProvider.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/ObjectInDataStoreStateMachine.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStore.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreInfo.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreLifeCycle.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreParameters.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreProvider.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/Scope.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotDataFactory.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotInfo.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotProfile.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotResult.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotService.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotStrategy.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/StorageAction.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/StorageCacheManager.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/StorageEvent.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/StoragePoolAllocator.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/StorageStrategyFactory.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/StrategyPriority.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/TemplateDataFactory.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/TemplateEvent.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/TemplateInfo.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/TemplateService.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/TemplateState.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/VMSnapshotOptions.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/VMSnapshotStrategy.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/VolumeDataFactory.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/VolumeInfo.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/VolumeService.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/ZoneScope.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/disktype/DiskFormat.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/disktype/QCOW2.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/disktype/Unknown.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/disktype/VHD.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/disktype/VHDX.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/disktype/VMDK.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/disktype/VolumeDiskType.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/disktype/VolumeDiskTypeBase.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/disktype/VolumeDiskTypeHelper.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/type/BaseImage.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/type/DataDisk.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/type/Iso.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/type/RootDisk.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/type/Unknown.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/type/VolumeType.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/type/VolumeTypeBase.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/type/VolumeTypeHelper.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/storage/command/CommandResult.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/storage/image/datastore/ImageStoreEntity.java create mode 100644 cosmic-core/engine/api/src/main/java/org/apache/cloudstack/storage/image/datastore/ImageStoreInfo.java create mode 100644 cosmic-core/engine/api/src/main/resources/META-INF/cloudstack/core/spring-engine-api-core-context.xml create mode 100644 cosmic-core/engine/api/src/test/java/org/apache/cloudstack/engine/subsystem/api/storage/ScopeTest.java create mode 100644 cosmic-core/engine/api/src/test/java/org/apache/cloudstack/engine/subsystem/api/storage/type/VolumeTypeHelperTest.java create mode 100644 cosmic-core/engine/components-api/pom.xml create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/agent/AgentManager.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/agent/Listener.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/alert/AlertManager.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/capacity/CapacityManager.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/deploy/DeploymentPlanningManager.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/event/UsageEventUtils.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/ha/HighAvailabilityManager.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/hypervisor/HypervisorGuruManager.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/network/IpAddressManager.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/network/NetworkStateListener.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/network/addr/PublicIp.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/network/lb/LoadBalancingRulesManager.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/network/rules/FirewallManager.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/network/rules/RulesManager.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/network/rules/StaticNatRuleImpl.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/network/vpc/NetworkACLManager.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/resource/Discoverer.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/resource/ResourceStateAdapter.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/template/TemplateManager.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/vm/ReservationContextImpl.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/vm/VirtualMachineProfileImpl.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/vm/VmWork.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/vm/VmWorkAttachVolume.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/vm/VmWorkConstants.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/vm/VmWorkDetachVolume.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/vm/VmWorkExtractVolume.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/vm/VmWorkJobHandler.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/vm/VmWorkJobHandlerProxy.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/vm/VmWorkMigrateVolume.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/vm/VmWorkResizeVolume.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/vm/VmWorkSerializer.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/vm/VmWorkTakeVolumeSnapshot.java create mode 100644 cosmic-core/engine/components-api/src/main/java/com/cloud/vm/snapshot/VMSnapshotManager.java create mode 100644 cosmic-core/engine/components-api/src/main/java/org/apache/cloudstack/compute/ComputeGuru.java create mode 100644 cosmic-core/engine/components-api/src/main/resources/META-INF/cloudstack/core/spring-engine-components-api-core-context.xml create mode 100644 cosmic-core/engine/network/pom.xml create mode 100644 cosmic-core/engine/network/src/main/java/org/apache/cloudstack/network/NetworkOrchestrator.java create mode 100755 cosmic-core/engine/orchestration/pom.xml create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentAttache.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentAttache.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentManagerImpl.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredDirectAgentAttache.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/agent/manager/ConnectedAgentAttache.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/agent/manager/DirectAgentAttache.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/agent/manager/DummyAttache.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/agent/manager/Routable.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/agent/manager/SynchronousListener.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/cluster/ClusteredAgentRebalanceService.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/cluster/agentlb/AgentLoadBalancerPlanner.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/cluster/agentlb/ClusterBasedAgentLoadBalancerPlanner.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/vm/ClusteredVirtualMachineManagerImpl.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachinePowerStateSync.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachinePowerStateSyncImpl.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/vm/VmWorkAddVmToNetwork.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/vm/VmWorkJobDispatcher.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/vm/VmWorkJobWakeupDispatcher.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/vm/VmWorkMigrate.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/vm/VmWorkMigrateAway.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/vm/VmWorkMigrateForScale.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/vm/VmWorkMigrateWithStorage.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/vm/VmWorkReboot.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/vm/VmWorkReconfigure.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/vm/VmWorkRemoveNicFromVm.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/vm/VmWorkRemoveVmFromNetwork.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/vm/VmWorkStart.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/vm/VmWorkStop.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/com/cloud/vm/VmWorkStorageMigration.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/VMEntityManager.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/VMEntityManagerImpl.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/VirtualMachineEntityImpl.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/ClusterEntityImpl.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/DataCenterResourceManager.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/DataCenterResourceManagerImpl.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/HostEntityImpl.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/PodEntityImpl.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/ZoneEntityImpl.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/ClusterDetailsVO.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/DcDetailVO.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineCluster.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineClusterVO.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineDataCenter.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineDataCenterVO.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineHost.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineHostPodVO.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineHostVO.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EnginePod.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/DcDetailsDao.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/DcDetailsDaoImpl.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/EngineClusterDao.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/EngineClusterDaoImpl.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/EngineDataCenterDao.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/EngineDataCenterDaoImpl.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/EngineHostDao.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/EngineHostDaoImpl.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/EngineHostPodDao.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/EngineHostPodDaoImpl.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/HostDetailsDao.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/HostDetailsDaoImpl.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/HostTagsDao.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/HostTagsDaoImpl.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java create mode 100644 cosmic-core/engine/orchestration/src/main/java/org/apache/cloudstack/engine/service/api/ProvisioningServiceImpl.java create mode 100644 cosmic-core/engine/orchestration/src/main/resources/META-INF/cloudstack/core/spring-engine-orchestration-core-context.xml create mode 100644 cosmic-core/engine/orchestration/src/test/java/com/cloud/agent/manager/ConnectedAgentAttacheTest.java create mode 100644 cosmic-core/engine/orchestration/src/test/java/com/cloud/agent/manager/DirectAgentAttacheTest.java create mode 100644 cosmic-core/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java create mode 100644 cosmic-core/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestratorTest.java create mode 100644 cosmic-core/engine/orchestration/src/test/java/org/apache/cloudstack/engine/provisioning/test/ChildTestConfiguration.java create mode 100644 cosmic-core/engine/orchestration/src/test/java/org/apache/cloudstack/engine/provisioning/test/ProvisioningTest.java create mode 100644 cosmic-core/engine/orchestration/src/test/resource/provisioningContext.xml create mode 100644 cosmic-core/engine/pom.xml create mode 100644 cosmic-core/engine/schema/pom.xml create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/alert/AlertVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/alert/dao/AlertDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/alert/dao/AlertDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/capacity/CapacityVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/certificate/CertificateVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/certificate/dao/CertificateDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/certificate/dao/CertificateDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/cluster/agentlb/HostTransferMapVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/cluster/agentlb/dao/HostTransferMapDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/cluster/agentlb/dao/HostTransferMapDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/configuration/ResourceCountVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/configuration/ResourceLimitVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/configuration/dao/ResourceCountDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/configuration/dao/ResourceCountDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/configuration/dao/ResourceLimitDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/configuration/dao/ResourceLimitDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/AccountVlanMapVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/ClusterVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/ClusterVSMMapVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/DataCenterDetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/DataCenterIpAddressVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/DataCenterLinkLocalIpAddressVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/DataCenterVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/DataCenterVnetVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/DomainVlanMapVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/HostPodVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/PodCluster.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/PodVlanMapVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/PodVlanVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/StorageNetworkIpAddressVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/StorageNetworkIpRangeVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/VlanVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/AccountVlanMapDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/AccountVlanMapDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/ClusterVSMMapDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/ClusterVSMMapDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterIpAddressDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterIpAddressDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterLinkLocalIpAddressDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterLinkLocalIpAddressDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterVnetDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterVnetDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/DomainVlanMapDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/DomainVlanMapDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/HostPodDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/HostPodDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/PodVlanDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/PodVlanDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/PodVlanMapDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/PodVlanMapDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/StorageNetworkIpAddressDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/StorageNetworkIpAddressDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/StorageNetworkIpRangeDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/StorageNetworkIpRangeDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/VlanDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/dc/dao/VlanDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/domain/DomainVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/domain/dao/DomainDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/domain/dao/DomainDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/event/EventVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/event/UsageEventDetailsVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/event/UsageEventVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/event/dao/EventDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/event/dao/EventDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/event/dao/UsageEventDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/event/dao/UsageEventDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/event/dao/UsageEventDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/event/dao/UsageEventDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/gpu/HostGpuGroupsVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/gpu/VGPUTypesVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/gpu/dao/HostGpuGroupsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/gpu/dao/HostGpuGroupsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/gpu/dao/VGPUTypesDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/gpu/dao/VGPUTypesDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/host/DetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/host/HostTagVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/host/HostVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/host/dao/HostDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/host/dao/HostDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/host/dao/HostDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/host/dao/HostTagsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/host/dao/HostTagsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/hypervisor/HypervisorCapabilitiesVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/hypervisor/dao/HypervisorCapabilitiesDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/hypervisor/dao/HypervisorCapabilitiesDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/LBHealthCheckPolicyVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/UserIpv6AddressVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/VpnUserVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/as/AutoScalePolicyConditionMapVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/as/AutoScalePolicyVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmGroupPolicyMapVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmGroupVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmGroupVmMapVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmProfileVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/as/ConditionVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/as/CounterVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScalePolicyConditionMapDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScalePolicyConditionMapDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScalePolicyDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScalePolicyDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmGroupDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmGroupDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmGroupPolicyMapDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmGroupPolicyMapDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmGroupVmMapDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmGroupVmMapDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmProfileDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmProfileDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/as/dao/ConditionDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/as/dao/ConditionDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/as/dao/CounterDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/as/dao/CounterDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/AccountGuestVlanMapDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/AccountGuestVlanMapDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/AccountGuestVlanMapVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/ExternalFirewallDeviceDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/ExternalFirewallDeviceDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/ExternalFirewallDeviceVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/ExternalLoadBalancerDeviceDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/ExternalLoadBalancerDeviceDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/ExternalLoadBalancerDeviceVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesCidrsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesCidrsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesCidrsVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/IPAddressVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/InlineLoadBalancerNicMapDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/InlineLoadBalancerNicMapDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/InlineLoadBalancerNicMapVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/LBHealthCheckPolicyDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/LBHealthCheckPolicyDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerCertMapDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerCertMapDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerCertMapVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVMMapDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVMMapDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVMMapVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/MonitoringServiceDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/MonitoringServiceDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/MonitoringServiceVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/NetworkAccountDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/NetworkAccountDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/NetworkAccountVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/NetworkDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/NetworkDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/NetworkDetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/NetworkDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/NetworkDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/NetworkDomainDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/NetworkDomainDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/NetworkDomainVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/NetworkExternalFirewallDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/NetworkExternalFirewallDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/NetworkExternalFirewallVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/NetworkExternalLoadBalancerDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/NetworkExternalLoadBalancerDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/NetworkExternalLoadBalancerVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/NetworkOpDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/NetworkOpDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/NetworkOpVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/NetworkRuleConfigDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/NetworkRuleConfigDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/NetworkRuleConfigVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/NetworkServiceMapDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/NetworkServiceMapDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/NetworkServiceMapVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/NetworkVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/OpRouterMonitorServiceDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/OpRouterMonitorServiceDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/OpRouterMonitorServiceVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkIsolationMethodDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkIsolationMethodVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkServiceProviderDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkServiceProviderDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkServiceProviderVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkTagDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkTagVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkTrafficTypeDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkTrafficTypeDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkTrafficTypeVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/RouterNetworkDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/RouterNetworkDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/RouterNetworkVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteCustomerGatewayDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteCustomerGatewayDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteCustomerGatewayVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnConnectionDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnConnectionDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnConnectionVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/SslCertDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/SslCertDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/SslCertVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/UserIpv6AddressDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/UserIpv6AddressDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/VirtualRouterProviderDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/VirtualRouterProviderDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/VpnUserDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/dao/VpnUserDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/element/VirtualRouterProviderVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/rules/FirewallRuleVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/rules/PortForwardingRuleVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/security/SecurityGroupRuleVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/security/SecurityGroupRulesVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/security/SecurityGroupVMMapVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/security/SecurityGroupVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/security/SecurityGroupWork.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/security/SecurityGroupWorkVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/security/VmRulesetLogVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupRuleDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupRuleDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupRulesDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupRulesDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupVMMapDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupVMMapDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupWorkDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupWorkDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/security/dao/VmRulesetLogDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/security/dao/VmRulesetLogDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/NetworkACLItemCidrsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/NetworkACLItemCidrsVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/NetworkACLItemDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/NetworkACLItemVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/NetworkACLVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/PrivateIpVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/StaticRouteVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/VpcGatewayVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingServiceMapVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/VpcServiceMapVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/VpcVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/dao/NetworkACLDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/dao/NetworkACLDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/dao/NetworkACLItemCidrsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/dao/NetworkACLItemDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/dao/PrivateIpDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/dao/PrivateIpDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/dao/StaticRouteDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/dao/StaticRouteDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcGatewayDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcGatewayDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingServiceMapDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingServiceMapDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcServiceMapDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcServiceMapDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/offerings/NetworkOfferingDetailsVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/offerings/NetworkOfferingServiceMapVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/offerings/NetworkOfferingVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingServiceMapDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingServiceMapDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/projects/ProjectAccountVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/projects/ProjectInvitationVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/projects/ProjectVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/projects/dao/ProjectAccountDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/projects/dao/ProjectDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/projects/dao/ProjectDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/projects/dao/ProjectInvitationDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/projects/dao/ProjectInvitationDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/secstorage/CommandExecLogDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/secstorage/CommandExecLogDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/secstorage/CommandExecLogVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/service/ServiceOfferingDetailsVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/DiskOfferingVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/GuestOSCategoryVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/GuestOSHypervisorVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/GuestOSVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/LaunchPermissionVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/SnapshotPolicyVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/SnapshotScheduleVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/SnapshotVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/StoragePoolHostAssoc.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/StoragePoolHostVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/StoragePoolWorkVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/UploadVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/VMTemplateDetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/VMTemplateHostVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/VMTemplateStoragePoolVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/VMTemplateVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/VMTemplateZoneVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/VolumeDetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/VolumeHostVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/VolumeVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSCategoryDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSCategoryDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSHypervisorDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSHypervisorDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/LaunchPermissionDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/LaunchPermissionDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDetailsVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotPolicyDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotPolicyDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotScheduleDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotScheduleDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolWorkDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolWorkDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/UploadDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/UploadDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateHostDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateHostDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplatePoolDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplatePoolDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateZoneDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateZoneDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/VolumeHostDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/storage/dao/VolumeHostDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/tags/ResourceTagVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/tags/dao/ResourceTagDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/tags/dao/ResourceTagsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/DatabaseCreator.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/DatabaseIntegrityChecker.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/DatabaseUpgradeChecker.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/BareMetalRemovalUpdater.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/DatabaseAccessObject.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/DbUpgrade.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/DbUpgradeUtils.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/LegacyDbUpgrade.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade40to41.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade410to420.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade420to421.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade421to430.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade430to440.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade431to440.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade432to440.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade440to441.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade441to442.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade442to450.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade443to444.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade443to450.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade444to450.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade450to451.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade451to452.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade452to453.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade452to460.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade453to460.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade460to461.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade461to470.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade470to471.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade471to480.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade480to500.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade500to501.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade501to510.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/UpgradeSnapshot217to224.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/UpgradeSnapshot223to224.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/VersionDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/VersionDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/upgrade/dao/VersionVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/ExternalPublicIpStatisticsVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/UsageIPAddressVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/UsageJobVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/UsageLoadBalancerPolicyVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/UsageNetworkOfferingVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/UsageNetworkVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/UsagePortForwardingRuleVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/UsageSecurityGroupVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/UsageStorageVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/UsageVMInstanceVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/UsageVMSnapshotVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/UsageVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/UsageVPNUserVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/UsageVmDiskVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/UsageVolumeVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/ExternalPublicIpStatisticsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/ExternalPublicIpStatisticsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsageDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsageDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsageIPAddressDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsageIPAddressDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsageJobDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsageJobDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsageLoadBalancerPolicyDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsageLoadBalancerPolicyDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworkDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworkDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworkOfferingDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworkOfferingDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsagePortForwardingRuleDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsagePortForwardingRuleDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsageSecurityGroupDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsageSecurityGroupDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsageStorageDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsageStorageDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsageVMInstanceDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsageVMInstanceDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsageVMSnapshotDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsageVMSnapshotDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsageVPNUserDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsageVPNUserDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsageVmDiskDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsageVmDiskDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsageVolumeDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/usage/dao/UsageVolumeDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/user/AccountDetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/user/AccountDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/user/AccountDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/user/AccountVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/user/SSHKeyPairVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/user/UserAccountVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/user/UserStatisticsVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/user/UserStatsLogVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/user/UserVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/user/VmDiskStatisticsVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/user/dao/AccountDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/user/dao/AccountDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/user/dao/SSHKeyPairDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/user/dao/SSHKeyPairDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/user/dao/UserAccountDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/user/dao/UserAccountDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/user/dao/UserDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/user/dao/UserDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/user/dao/UserStatisticsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/user/dao/UserStatisticsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/user/dao/UserStatsLogDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/user/dao/UserStatsLogDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/user/dao/VmDiskStatisticsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/user/dao/VmDiskStatisticsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/ConsoleProxyVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/DomainRouterVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/InstanceGroupVMMapVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/InstanceGroupVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/ItWorkDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/ItWorkDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/ItWorkVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/NicDetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/NicVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/SecondaryStorageVmVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/UserVmCloneSettingVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/UserVmDetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/UserVmVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleProxyDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleProxyDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/DomainRouterDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/DomainRouterDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/InstanceGroupDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/InstanceGroupDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/InstanceGroupVMMapDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/InstanceGroupVMMapDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/NicDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/NicDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/NicIpAliasDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/NicIpAliasDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/NicIpAliasVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/SecondaryStorageVmDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/SecondaryStorageVmDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/UserVmCloneSettingDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/UserVmCloneSettingDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/UserVmDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/UserVmDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/UserVmData.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/UserVmDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/UserVmDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/snapshot/VMSnapshotDetailsVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/snapshot/VMSnapshotVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/snapshot/dao/VMSnapshotDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/snapshot/dao/VMSnapshotDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/snapshot/dao/VMSnapshotDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/com/cloud/vm/snapshot/dao/VMSnapshotDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/affinity/AffinityGroupDomainMapVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/affinity/AffinityGroupVMMapVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/affinity/AffinityGroupVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDomainMapDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDomainMapDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupVMMapDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupVMMapDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMComputeTagVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMEntityVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMNetworkMapVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMReservationVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMRootDiskTagVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VolumeReservationVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMComputeTagDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMComputeTagDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMEntityDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMEntityDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMNetworkMapDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMNetworkMapDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMReservationDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMReservationDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMRootDiskTagDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMRootDiskTagDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VolumeReservationDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VolumeReservationDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/lb/ApplicationLoadBalancerRuleVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/lb/dao/ApplicationLoadBalancerRuleDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/lb/dao/ApplicationLoadBalancerRuleDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/region/PortableIpDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/region/PortableIpDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/region/PortableIpRangeDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/region/PortableIpRangeDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/region/PortableIpRangeVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/region/PortableIpVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/region/RegionSyncVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/region/RegionVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/region/dao/RegionDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/region/dao/RegionDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancerDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancerLbRuleMapDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancerLbRuleMapDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancerLbRuleMapVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancerRuleDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancerRuleVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/AutoScaleVmGroupDetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/AutoScaleVmProfileDetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/DiskOfferingDetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/FirewallRuleDetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/LBHealthCheckPolicyDetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/LBStickinessPolicyDetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/NetworkACLItemDetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/NetworkACLListDetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/RemoteAccessVpnDetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBase.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/Site2SiteCustomerGatewayDetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/Site2SiteVpnConnectionDetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/Site2SiteVpnGatewayDetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/SnapshotPolicyDetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/UserDetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/UserIpAddressDetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/VpcDetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/VpcGatewayDetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/AutoScaleVmGroupDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/AutoScaleVmGroupDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/AutoScaleVmProfileDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/AutoScaleVmProfileDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/DiskOfferingDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/DiskOfferingDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/FirewallRuleDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/FirewallRuleDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/LBHealthCheckPolicyDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/LBHealthCheckPolicyDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/LBStickinessPolicyDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/LBStickinessPolicyDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/NetworkACLItemDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/NetworkACLItemDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/NetworkACLListDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/NetworkACLListDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/RemoteAccessVpnDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/RemoteAccessVpnDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/Site2SiteCustomerGatewayDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/Site2SiteCustomerGatewayDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/Site2SiteVpnConnectionDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/Site2SiteVpnConnectionDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/Site2SiteVpnGatewayDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/Site2SiteVpnGatewayDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/SnapshotPolicyDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/SnapshotPolicyDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/UserDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/UserDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/UserIpAddressDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/UserIpAddressDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/VpcDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/VpcDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/VpcGatewayDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/dao/VpcGatewayDetailsDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImpl.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/StoragePoolDetailVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/StoragePoolDetailsDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/StoragePoolVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/TemplateDataStoreDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/TemplateDataStoreVO.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/VolumeDataStoreDao.java create mode 100644 cosmic-core/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/VolumeDataStoreVO.java create mode 100644 cosmic-core/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml create mode 100644 cosmic-core/engine/schema/src/main/resources/META-INF/cloudstack/system/spring-engine-schema-system-checkers-context.xml create mode 100644 cosmic-core/engine/schema/src/test/java/com/cloud/upgrade/dao/BareMetalRemovalUpdaterTest.java create mode 100644 cosmic-core/engine/schema/src/test/java/com/cloud/upgrade/dao/DatabaseAccessObjectTest.java create mode 100644 cosmic-core/engine/schema/src/test/java/com/cloud/upgrade/dao/DbUpgradeUtilsTest.java create mode 100644 cosmic-core/engine/storage/cache/pom.xml create mode 100644 cosmic-core/engine/storage/cache/src/main/java/org/apache/cloudstack/storage/cache/allocator/StorageCacheAllocator.java create mode 100644 cosmic-core/engine/storage/cache/src/main/java/org/apache/cloudstack/storage/cache/allocator/StorageCacheRandomAllocator.java create mode 100644 cosmic-core/engine/storage/cache/src/main/java/org/apache/cloudstack/storage/cache/manager/StorageCacheManagerImpl.java create mode 100644 cosmic-core/engine/storage/cache/src/main/java/org/apache/cloudstack/storage/cache/manager/StorageCacheReplacementAlgorithm.java create mode 100644 cosmic-core/engine/storage/cache/src/main/java/org/apache/cloudstack/storage/cache/manager/StorageCacheReplacementAlgorithmLRU.java create mode 100644 cosmic-core/engine/storage/cache/src/main/resources/META-INF/cloudstack/core/spring-engine-storage-cache-core-context.xml create mode 100644 cosmic-core/engine/storage/datamotion/pom.xml create mode 100644 cosmic-core/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategy.java create mode 100644 cosmic-core/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/DataMotionServiceImpl.java create mode 100644 cosmic-core/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java create mode 100644 cosmic-core/engine/storage/datamotion/src/main/resources/META-INF/cloudstack/core/spring-engine-storage-datamotion-core-context.xml create mode 100644 cosmic-core/engine/storage/datamotion/src/main/resources/META-INF/cloudstack/storage/spring-engine-storage-datamotion-storage-context.xml create mode 100644 cosmic-core/engine/storage/image/pom.xml create mode 100644 cosmic-core/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/TemplateDataFactoryImpl.java create mode 100644 cosmic-core/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/TemplateServiceImpl.java create mode 100644 cosmic-core/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/manager/ImageStoreProviderManagerImpl.java create mode 100644 cosmic-core/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/store/ImageStoreImpl.java create mode 100644 cosmic-core/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/store/TemplateObject.java create mode 100644 cosmic-core/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/store/lifecycle/ImageStoreLifeCycle.java create mode 100644 cosmic-core/engine/storage/image/src/main/resources/META-INF/cloudstack/core/spring-engine-storage-image-core-context.xml create mode 100644 cosmic-core/engine/storage/pom.xml create mode 100644 cosmic-core/engine/storage/snapshot/pom.xml create mode 100644 cosmic-core/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotDataFactoryImpl.java create mode 100644 cosmic-core/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotObject.java create mode 100644 cosmic-core/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotServiceImpl.java create mode 100644 cosmic-core/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotStateMachineManager.java create mode 100644 cosmic-core/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotStateMachineManagerImpl.java create mode 100644 cosmic-core/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotStrategyBase.java create mode 100644 cosmic-core/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java create mode 100644 cosmic-core/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/XenserverSnapshotStrategy.java create mode 100644 cosmic-core/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/DefaultVMSnapshotStrategy.java create mode 100644 cosmic-core/engine/storage/snapshot/src/main/resources/META-INF/cloudstack/core/spring-engine-storage-snapshot-core-context.xml create mode 100644 cosmic-core/engine/storage/snapshot/src/main/resources/META-INF/cloudstack/storage/spring-engine-storage-snapshot-storage-context.xml create mode 100644 cosmic-core/engine/storage/snapshot/src/test/java/src/SnapshotDataFactoryTest.java create mode 100644 cosmic-core/engine/storage/snapshot/src/test/java/src/VMSnapshotStrategyTest.java create mode 100644 cosmic-core/engine/storage/snapshot/src/test/resources/SnapshotManagerTestContext.xml create mode 100644 cosmic-core/engine/storage/snapshot/src/test/resources/db.properties create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/BaseType.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/EndPoint.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/LocalHostEndpoint.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/RemoteHostEndPoint.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/ClusterScopeStoragePoolAllocator.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/GarbageCollectingStoragePoolAllocator.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/LocalStoragePoolAllocator.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/UseLocalForRootAllocator.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/ZoneWideStoragePoolAllocator.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/backup/SnapshotOnBackupStoreInfo.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/backup/datastore/BackupStoreInfo.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/DataObjectManager.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/DataObjectManagerImpl.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/DataStore.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/DataStoreManagerImpl.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/ObjectInDataStoreManager.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/ObjectInDataStoreManagerImpl.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/PrimaryDataStoreProviderManager.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/TemplateInDataStore.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/protocol/DataStoreProtocol.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/provider/DataStoreProviderManagerImpl.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/db/ObjectInDataStoreDao.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/db/ObjectInDataStoreDaoImpl.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/db/ObjectInDataStoreVO.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/endpoint/DefaultEndPointSelector.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/helper/HypervisorHelper.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/helper/HypervisorHelperImpl.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/helper/StorageStrategyFactoryImpl.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/helper/VMSnapshotHelperImpl.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/image/ImageStoreDriver.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/image/TemplateEntityImpl.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/image/datastore/ImageStoreHelper.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/image/datastore/ImageStoreProviderManager.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/image/db/ImageStoreDaoImpl.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/image/db/ImageStoreDetailsDaoImpl.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/image/db/SnapshotDataStoreDaoImpl.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/image/db/TemplateDataStoreDaoImpl.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/image/db/VolumeDataStoreDaoImpl.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/image/format/BAREMETAL.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/image/format/ISO.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/image/format/ImageFormat.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/image/format/ImageFormatHelper.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/image/format/OVA.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/image/format/QCOW2.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/image/format/RAW.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/image/format/Unknown.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/image/format/VHD.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/image/format/VHDX.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/image/motion/ImageMotionService.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotEntityImpl.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/vmsnapshot/VMSnapshotHelper.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/TemplateOnPrimaryDataStoreInfo.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/VolumeEvent.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/db/PrimaryDataStoreDetailsDaoImpl.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/db/TemplatePrimaryDataStoreDao.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/db/TemplatePrimaryDataStoreDaoImpl.java create mode 100644 cosmic-core/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/db/TemplatePrimaryDataStoreVO.java create mode 100644 cosmic-core/engine/storage/src/main/resources/META-INF/cloudstack/core/spring-engine-storage-core-context.xml create mode 100644 cosmic-core/engine/storage/src/main/resources/META-INF/cloudstack/storage-allocator/module.properties create mode 100644 cosmic-core/engine/storage/src/main/resources/META-INF/cloudstack/storage-allocator/spring-engine-storage-storage-allocator-context.xml create mode 100644 cosmic-core/engine/storage/src/test/java/org/apache/cloudstack/engine/subsystem/api/storage/StrategyPriorityTest.java create mode 100644 cosmic-core/engine/storage/src/test/java/org/apache/cloudstack/storage/BaseTypeTest.java create mode 100644 cosmic-core/engine/storage/storage.ucls create mode 100644 cosmic-core/engine/storage/volume/pom.xml create mode 100644 cosmic-core/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/PrimaryDataStoreImpl.java create mode 100644 cosmic-core/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/manager/PrimaryDataStoreProviderManagerImpl.java create mode 100644 cosmic-core/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/manager/data model.ucls create mode 100644 cosmic-core/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/provider/DefaultHostListener.java create mode 100644 cosmic-core/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/type/DataStoreType.java create mode 100644 cosmic-core/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/type/ISCSI.java create mode 100644 cosmic-core/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/type/NetworkFileSystem.java create mode 100644 cosmic-core/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/type/SharedMount.java create mode 100644 cosmic-core/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeDataFactoryImpl.java create mode 100644 cosmic-core/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeObject.java create mode 100644 cosmic-core/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java create mode 100644 cosmic-core/engine/storage/volume/src/main/resources/META-INF/cloudstack/core/spring-engine-storage-volume-core-context.xml create mode 100644 cosmic-core/engine/storage/volume/src/test/java/org/apache/cloudstack/storage/volume/VolumeObjectTest.java create mode 100644 cosmic-core/engine/storage/volume/src/test/java/org/apache/cloudstack/storage/volume/test/ConfiguratorTest.java create mode 100644 cosmic-core/engine/storage/volume/src/test/java/org/apache/cloudstack/storage/volume/test/Server.java create mode 100644 cosmic-core/engine/storage/volume/src/test/java/org/apache/cloudstack/storage/volume/test/Server1.java create mode 100644 cosmic-core/engine/storage/volume/src/test/java/org/apache/cloudstack/storage/volume/test/TestConfiguration.java create mode 100644 cosmic-core/engine/storage/volume/src/test/java/org/apache/cloudstack/storage/volume/test/TestInProcessAsync.java create mode 100644 cosmic-core/engine/storage/volume/src/test/resources/testContext.xml create mode 100644 cosmic-core/framework/cluster/pom.xml create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/ActiveFencingException.java create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/ClusterFenceManager.java create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/ClusterFenceManagerImpl.java create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/ClusterInvalidSessionException.java create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/ClusterManager.java create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/ClusterManagerImpl.java create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/ClusterManagerListener.java create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/ClusterManagerMBean.java create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/ClusterManagerMBeanImpl.java create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/ClusterManagerMessage.java create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/ClusterNodeJoinEventArgs.java create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/ClusterNodeLeftEventArgs.java create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/ClusterService.java create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceAdapter.java create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/ClusterServicePdu.java create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceRequestPdu.java create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletAdapter.java create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletContainer.java create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletHttpHandler.java create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletImpl.java create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/ManagementServerHost.java create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/ManagementServerHostPeerVO.java create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/ManagementServerHostVO.java create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/RemoteMethodConstants.java create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/dao/ManagementServerHostDao.java create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/dao/ManagementServerHostDaoImpl.java create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/dao/ManagementServerHostPeerDao.java create mode 100644 cosmic-core/framework/cluster/src/main/java/com/cloud/cluster/dao/ManagementServerHostPeerDaoImpl.java create mode 100644 cosmic-core/framework/cluster/src/main/resources/META-INF/cloudstack/core/spring-framework-cluster-core-context.xml create mode 100644 cosmic-core/framework/cluster/src/test/java/com/cloud/cluster/ClusterServiceServletAdapterTest.java create mode 100644 cosmic-core/framework/config/pom.xml create mode 100644 cosmic-core/framework/config/src/main/java/org/apache/cloudstack/config/Configuration.java create mode 100644 cosmic-core/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigDepot.java create mode 100644 cosmic-core/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigDepotAdmin.java create mode 100644 cosmic-core/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java create mode 100644 cosmic-core/framework/config/src/main/java/org/apache/cloudstack/framework/config/Configurable.java create mode 100644 cosmic-core/framework/config/src/main/java/org/apache/cloudstack/framework/config/ScopedConfigStorage.java create mode 100644 cosmic-core/framework/config/src/main/java/org/apache/cloudstack/framework/config/dao/ConfigurationDao.java create mode 100644 cosmic-core/framework/config/src/main/java/org/apache/cloudstack/framework/config/dao/ConfigurationDaoImpl.java create mode 100644 cosmic-core/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigDepotImpl.java create mode 100644 cosmic-core/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationVO.java create mode 100644 cosmic-core/framework/config/src/main/resources/META-INF/cloudstack/system/spring-framework-config-system-context-inheritable.xml create mode 100644 cosmic-core/framework/config/src/main/resources/META-INF/cloudstack/system/spring-framework-config-system-context.xml create mode 100644 cosmic-core/framework/config/src/test/java/org/apache/cloudstack/framework/config/ConfigKeyTest.java create mode 100644 cosmic-core/framework/config/src/test/java/org/apache/cloudstack/framework/config/impl/ConfigDepotAdminTest.java create mode 100644 cosmic-core/framework/db/pom.xml create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/dao/EntityManagerImpl.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/crypt/EncryptionSecretKeyChanger.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/Attribute.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/ConnectionConcierge.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/ConnectionConciergeMBean.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/DB.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/DbUtil.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/EcInfo.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/Encrypt.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/Filter.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/GenericDao.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/GenericQueryBuilder.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/GenericSearchBuilder.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/GlobalLock.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/GroupBy.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/JoinBuilder.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/JoinType.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/Merovingian2.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/MerovingianMBean.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/QueryBuilder.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/ScriptRunner.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/SearchBase.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/SearchBuilder.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/SearchCriteria.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/SequenceFetcher.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/SqlGenerator.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/StateMachine.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/Transaction.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/TransactionAttachment.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/TransactionCallback.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/TransactionCallbackNoReturn.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/TransactionCallbackWithException.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/TransactionCallbackWithExceptionNoReturn.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/TransactionContextBuilder.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/TransactionContextInterceptor.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/TransactionContextListener.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/TransactionLegacy.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/TransactionMBean.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/TransactionMBeanImpl.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/TransactionStatus.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/UpdateBuilder.java create mode 100644 cosmic-core/framework/db/src/main/java/com/cloud/utils/db/UpdateFilter.java create mode 100644 cosmic-core/framework/db/src/main/resources/META-INF/cloudstack/system/spring-framework-db-system-context.xml create mode 100644 cosmic-core/framework/db/src/test/java/com/cloud/utils/DbUtilTest.java create mode 100644 cosmic-core/framework/db/src/test/java/com/cloud/utils/db/DbAnnotatedBase.java create mode 100644 cosmic-core/framework/db/src/test/java/com/cloud/utils/db/DbAnnotatedBaseDerived.java create mode 100644 cosmic-core/framework/db/src/test/java/com/cloud/utils/db/DbTestDao.java create mode 100644 cosmic-core/framework/db/src/test/java/com/cloud/utils/db/DbTestUtils.java create mode 100644 cosmic-core/framework/db/src/test/java/com/cloud/utils/db/DbTestVO.java create mode 100644 cosmic-core/framework/db/src/test/java/com/cloud/utils/db/DummyComponent.java create mode 100644 cosmic-core/framework/db/src/test/java/com/cloud/utils/db/ElementCollectionTest.java create mode 100644 cosmic-core/framework/db/src/test/java/com/cloud/utils/db/FilterTest.java create mode 100644 cosmic-core/framework/db/src/test/java/com/cloud/utils/db/GenericDaoBaseTest.java create mode 100644 cosmic-core/framework/db/src/test/java/com/cloud/utils/db/GlobalLockTest.java create mode 100644 cosmic-core/framework/db/src/test/java/com/cloud/utils/db/GroupByTest.java create mode 100644 cosmic-core/framework/db/src/test/java/com/cloud/utils/db/Merovingian2Test.java create mode 100644 cosmic-core/framework/db/src/test/java/com/cloud/utils/db/TestTransaction.java create mode 100644 cosmic-core/framework/db/src/test/java/com/cloud/utils/db/TransactionContextBuilderTest.java create mode 100644 cosmic-core/framework/db/src/test/java/com/cloud/utils/db/TransactionTest.java create mode 100644 cosmic-core/framework/db/src/test/resources/db.properties create mode 100644 cosmic-core/framework/events/pom.xml create mode 100644 cosmic-core/framework/events/src/main/java/org/apache/cloudstack/framework/events/Event.java create mode 100644 cosmic-core/framework/events/src/main/java/org/apache/cloudstack/framework/events/EventBus.java create mode 100644 cosmic-core/framework/events/src/main/java/org/apache/cloudstack/framework/events/EventBusException.java create mode 100644 cosmic-core/framework/events/src/main/java/org/apache/cloudstack/framework/events/EventSubscriber.java create mode 100644 cosmic-core/framework/events/src/main/java/org/apache/cloudstack/framework/events/EventTopic.java create mode 100644 cosmic-core/framework/ipc/pom.xml create mode 100644 cosmic-core/framework/ipc/src/main/java/com/cloud/agent/manager/Commands.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/async/AsyncCallFuture.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/async/AsyncCallbackDispatcher.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/async/AsyncCallbackDriver.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/async/AsyncCompletionCallback.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/async/AsyncRpcContext.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/async/InplaceAsyncCallbackDriver.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/async/Void.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/client/ClientMessageBus.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/client/ClientTransportConnection.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/client/ClientTransportEndpoint.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/client/ClientTransportEndpointSite.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/client/ClientTransportProvider.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/messagebus/MessageBus.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/messagebus/MessageBusBase.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/messagebus/MessageBusEndpoint.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/messagebus/MessageDetector.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/messagebus/MessageDispatcher.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/messagebus/MessageHandler.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/messagebus/MessageSubscriber.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/messagebus/PublishScope.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/rpc/RpcCallRequestPdu.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/rpc/RpcCallResponsePdu.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/rpc/RpcCallbackDispatcher.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/rpc/RpcCallbackListener.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/rpc/RpcClientCall.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/rpc/RpcClientCallImpl.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/rpc/RpcException.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/rpc/RpcIOException.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/rpc/RpcProvider.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/rpc/RpcProviderImpl.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/rpc/RpcServerCall.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/rpc/RpcServerCallImpl.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/rpc/RpcServiceDispatcher.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/rpc/RpcServiceEndpoint.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/rpc/RpcServiceHandler.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/rpc/RpcTimeoutException.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/serializer/JsonMessageSerializer.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/serializer/MessageSerializer.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/serializer/OnwireClassRegistry.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/serializer/OnwireName.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/server/ServerMessageBus.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/server/ServerTransportProvider.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/transport/TransportAddress.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/transport/TransportAddressMapper.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/transport/TransportAttachRequestPdu.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/transport/TransportAttachResponsePdu.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/transport/TransportConnectRequestPdu.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/transport/TransportConnectResponsePdu.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/transport/TransportDataPdu.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/transport/TransportEndpoint.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/transport/TransportEndpointSite.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/transport/TransportMultiplexier.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/transport/TransportPdu.java create mode 100644 cosmic-core/framework/ipc/src/main/java/org/apache/cloudstack/framework/transport/TransportProvider.java create mode 100644 cosmic-core/framework/ipc/src/main/resources/META-INF/cloudstack/core/spring-framework-ipc-core-context.xml create mode 100644 cosmic-core/framework/ipc/src/test/java/org/apache/cloudstack/framework/codestyle/AsyncSampleCallee.java create mode 100644 cosmic-core/framework/ipc/src/test/java/org/apache/cloudstack/framework/codestyle/AsyncSampleEventDrivenStyleCaller.java create mode 100644 cosmic-core/framework/ipc/src/test/java/org/apache/cloudstack/framework/codestyle/AsyncSampleListenerStyleCaller.java create mode 100644 cosmic-core/framework/ipc/src/test/java/org/apache/cloudstack/framework/sampleserver/SampleManagementServer.java create mode 100644 cosmic-core/framework/ipc/src/test/java/org/apache/cloudstack/framework/sampleserver/SampleManagementServerApp.java create mode 100644 cosmic-core/framework/ipc/src/test/java/org/apache/cloudstack/framework/sampleserver/SampleManagerComponent.java create mode 100644 cosmic-core/framework/ipc/src/test/java/org/apache/cloudstack/framework/sampleserver/SampleManagerComponent2.java create mode 100644 cosmic-core/framework/ipc/src/test/java/org/apache/cloudstack/framework/sampleserver/SampleStoragePrepareAnswer.java create mode 100644 cosmic-core/framework/ipc/src/test/java/org/apache/cloudstack/framework/sampleserver/SampleStoragePrepareCommand.java create mode 100644 cosmic-core/framework/ipc/src/test/java/org/apache/cloudstack/messagebus/TestMessageBus.java create mode 100644 cosmic-core/framework/ipc/src/test/resources/MessageBusTestContext.xml create mode 100644 cosmic-core/framework/ipc/src/test/resources/SampleManagementServerAppContext.xml create mode 100644 cosmic-core/framework/ipc/src/test/resources/log4j-cloud.xml create mode 100644 cosmic-core/framework/jobs/pom.xml create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/AsyncJob.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/AsyncJobDispatcher.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/AsyncJobExecutionContext.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/AsyncJobMBean.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/AsyncJobManager.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/JobCancellationException.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/Outcome.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDao.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobDaoImpl.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobJoinMapDao.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobJoinMapDaoImpl.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobJournalDao.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/AsyncJobJournalDaoImpl.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/SyncQueueDao.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/SyncQueueDaoImpl.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/SyncQueueItemDao.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/SyncQueueItemDaoImpl.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/VmWorkJobDao.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/VmWorkJobDaoImpl.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobJoinMapVO.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobJournalVO.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobMBeanImpl.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobManagerImpl.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobMonitor.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobVO.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/JobSerializerHelper.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/OutcomeImpl.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/SyncQueueItem.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/SyncQueueItemVO.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/SyncQueueManager.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/SyncQueueManagerImpl.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/SyncQueueVO.java create mode 100644 cosmic-core/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/VmWorkJobVO.java create mode 100644 cosmic-core/framework/jobs/src/main/resources/META-INF/cloudstack/core/spring-framework-jobs-core-context.xml create mode 100644 cosmic-core/framework/jobs/src/test/java/org/apache/cloudstack/framework/jobs/AsyncJobManagerTest.java create mode 100644 cosmic-core/framework/jobs/src/test/java/org/apache/cloudstack/framework/jobs/AsyncJobManagerTestConfiguration.java create mode 100644 cosmic-core/framework/jobs/src/test/java/org/apache/cloudstack/framework/jobs/AsyncJobTestDashboard.java create mode 100644 cosmic-core/framework/jobs/src/test/java/org/apache/cloudstack/framework/jobs/AsyncJobTestDispatcher.java create mode 100644 cosmic-core/framework/jobs/src/test/resources/AsyncJobManagerTestContext.xml create mode 100644 cosmic-core/framework/jobs/src/test/resources/commonContext.xml create mode 100644 cosmic-core/framework/jobs/src/test/resources/db.properties create mode 100644 cosmic-core/framework/jobs/src/test/resources/log4j.properties create mode 100644 cosmic-core/framework/managed-context/pom.xml create mode 100644 cosmic-core/framework/managed-context/src/main/java/org/apache/cloudstack/managed/context/AbstractManagedContextListener.java create mode 100644 cosmic-core/framework/managed-context/src/main/java/org/apache/cloudstack/managed/context/ManagedContext.java create mode 100644 cosmic-core/framework/managed-context/src/main/java/org/apache/cloudstack/managed/context/ManagedContextListener.java create mode 100644 cosmic-core/framework/managed-context/src/main/java/org/apache/cloudstack/managed/context/ManagedContextRunnable.java create mode 100644 cosmic-core/framework/managed-context/src/main/java/org/apache/cloudstack/managed/context/ManagedContextTimerTask.java create mode 100644 cosmic-core/framework/managed-context/src/main/java/org/apache/cloudstack/managed/context/ManagedContextUtils.java create mode 100644 cosmic-core/framework/managed-context/src/main/java/org/apache/cloudstack/managed/context/impl/DefaultManagedContext.java create mode 100644 cosmic-core/framework/managed-context/src/main/java/org/apache/cloudstack/managed/threadlocal/ManagedThreadLocal.java create mode 100644 cosmic-core/framework/managed-context/src/test/java/org/apache/cloudstack/managed/context/impl/DefaultManagedContextTest.java create mode 100644 cosmic-core/framework/pom.xml create mode 100644 cosmic-core/framework/rest/pom.xml create mode 100644 cosmic-core/framework/rest/src/main/java/org/apache/cloudstack/framework/ws/jackson/CSJacksonAnnotationIntrospector.java create mode 100644 cosmic-core/framework/rest/src/main/java/org/apache/cloudstack/framework/ws/jackson/CSJacksonAnnotationModule.java create mode 100644 cosmic-core/framework/rest/src/main/java/org/apache/cloudstack/framework/ws/jackson/UriSerializer.java create mode 100644 cosmic-core/framework/rest/src/main/java/org/apache/cloudstack/framework/ws/jackson/UrisSerializer.java create mode 100644 cosmic-core/framework/rest/src/main/java/org/apache/cloudstack/framework/ws/jackson/Url.java create mode 100644 cosmic-core/framework/rest/src/test/java/org/apache/cloudstack/framework/ws/jackson/CSJacksonAnnotationTest.java create mode 100644 cosmic-core/framework/security/pom.xml create mode 100644 cosmic-core/framework/security/src/main/java/org/apache/cloudstack/framework/security/keys/KeysManager.java create mode 100644 cosmic-core/framework/security/src/main/java/org/apache/cloudstack/framework/security/keys/KeysManagerImpl.java create mode 100644 cosmic-core/framework/security/src/main/java/org/apache/cloudstack/framework/security/keystore/KeystoreDao.java create mode 100644 cosmic-core/framework/security/src/main/java/org/apache/cloudstack/framework/security/keystore/KeystoreDaoImpl.java create mode 100644 cosmic-core/framework/security/src/main/java/org/apache/cloudstack/framework/security/keystore/KeystoreManager.java create mode 100644 cosmic-core/framework/security/src/main/java/org/apache/cloudstack/framework/security/keystore/KeystoreManagerImpl.java create mode 100644 cosmic-core/framework/security/src/main/java/org/apache/cloudstack/framework/security/keystore/KeystoreVO.java create mode 100644 cosmic-core/framework/security/src/main/resources/META-INF/cloudstack/core/spring-framework-security-core-context.xml create mode 100644 cosmic-core/framework/spring/lifecycle/pom.xml create mode 100644 cosmic-core/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/AbstractBeanCollector.java create mode 100644 cosmic-core/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/AbstractSmartLifeCycle.java create mode 100644 cosmic-core/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/CloudStackExtendedLifeCycle.java create mode 100644 cosmic-core/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/CloudStackExtendedLifeCycleStart.java create mode 100644 cosmic-core/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/CloudStackLog4jSetup.java create mode 100644 cosmic-core/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/ConfigDepotLifeCycle.java create mode 100644 cosmic-core/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/DumpRegistry.java create mode 100644 cosmic-core/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/ExtensionRegistry.java create mode 100644 cosmic-core/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/PluggableServiceLifecycle.java create mode 100644 cosmic-core/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/RegistryLifecycle.java create mode 100644 cosmic-core/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/RegistryUtils.java create mode 100644 cosmic-core/framework/spring/module/pom.xml create mode 100644 cosmic-core/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/context/ResourceApplicationContext.java create mode 100644 cosmic-core/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/factory/CloudStackSpringContext.java create mode 100644 cosmic-core/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/factory/ModuleBasedContextFactory.java create mode 100644 cosmic-core/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/factory/QuietLoaderFactory.java create mode 100644 cosmic-core/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/locator/ModuleDefinitionLocator.java create mode 100644 cosmic-core/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/locator/impl/ClasspathModuleDefinitionLocator.java create mode 100644 cosmic-core/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/model/ModuleDefinition.java create mode 100644 cosmic-core/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/model/ModuleDefinitionSet.java create mode 100644 cosmic-core/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/model/impl/DefaultModuleDefinition.java create mode 100644 cosmic-core/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/model/impl/DefaultModuleDefinitionSet.java create mode 100644 cosmic-core/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/util/Main.java create mode 100644 cosmic-core/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/util/ModuleLocationUtils.java create mode 100644 cosmic-core/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/web/CloudStackContextLoaderListener.java create mode 100644 cosmic-core/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/web/ModuleBasedFilter.java create mode 100644 cosmic-core/framework/spring/module/src/main/resources/org/apache/cloudstack/spring/module/model/impl/defaults-context.xml create mode 100644 cosmic-core/framework/spring/module/src/test/java/org/apache/cloudstack/spring/module/factory/InitTest.java create mode 100644 cosmic-core/framework/spring/module/src/test/java/org/apache/cloudstack/spring/module/factory/ModuleBasedContextFactoryTest.java create mode 100644 cosmic-core/framework/spring/module/src/test/java/org/apache/cloudstack/spring/module/locator/impl/ClasspathModuleDefinitionSetLocatorTest.java create mode 100644 cosmic-core/framework/spring/module/src/test/java/org/apache/cloudstack/spring/module/model/impl/DefaultModuleDefinitionTest.java create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testfiles/all/defaults.properties create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testfiles/all/empty-context-inheritable.xml create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testfiles/all/empty-context.xml create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testfiles/all/empty2-context-inheritable.xml create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testfiles/all/empty2-context.xml create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testfiles/all/module.properties create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testfiles/all/test2-defaults.properties create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testfiles/badname/module.properties create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testfiles/blankname/module.properties create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testfiles/good/empty-context.xml create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testfiles/good/module.properties create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testfiles/missingname/module.properties create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testfiles/wrongname/module.properties create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testhierarchy/base/module.properties create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testhierarchy/base/test-context-inheritable.xml create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testhierarchy/base/test-context.xml create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testhierarchy/child1-1/module.properties create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testhierarchy/child1-1/test-context.xml create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testhierarchy/child1/module.properties create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testhierarchy/child1/test-context-override.xml create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testhierarchy/child1/test-context.xml create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testhierarchy/child2/module.properties create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testhierarchy/child2/test-context.xml create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testhierarchy/excluded/module.properties create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testhierarchy/excluded/test-context.xml create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testhierarchy/excluded2/module.properties create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testhierarchy/excluded2/test-context.xml create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testhierarchy/orphan-of-excluded/defaults.properties create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testhierarchy/orphan-of-excluded/module.properties create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testhierarchy/orphan-of-excluded/test-context.xml create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testhierarchy/orphan1/module.properties create mode 100644 cosmic-core/framework/spring/module/src/test/resources/testhierarchy/orphan1/test-context.xml create mode 100644 cosmic-core/nucleo/pom.xml create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/IAgentControl.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/IAgentControlListener.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/StartupCommandProcessor.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/AgentControlAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/AgentControlCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/AttachIsoCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/AttachOrDettachConfigDriveCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/BackupSnapshotAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/BackupSnapshotCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/BumpUpPriorityCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/CancelCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/ChangeAgentAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/ChangeAgentCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/CheckHealthAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/CheckHealthCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/CheckNetworkAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/CheckNetworkCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/CheckOnHostAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/CheckOnHostCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/CheckRouterAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/CheckRouterCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/CheckS2SVpnConnectionsAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/CheckS2SVpnConnectionsCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/CheckStateAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/CheckStateCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/CheckVirtualMachineAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/CheckVirtualMachineCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/CleanupNetworkRulesCmd.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/ClusterVMMetaDataSyncAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/ClusterVMMetaDataSyncCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/ComputeChecksumCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/ConsoleAccessAuthenticationAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/ConsoleAccessAuthenticationCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/ConsoleProxyLoadReportCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/CreatePrivateTemplateFromSnapshotCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/CreatePrivateTemplateFromVolumeCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/CreateStoragePoolCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/CreateVMSnapshotAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/CreateVMSnapshotCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/CreateVolumeFromSnapshotAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/CreateVolumeFromSnapshotCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/CreateVolumeFromVMSnapshotAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/CreateVolumeFromVMSnapshotCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/CronCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/DeleteSnapshotsDirCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/DeleteStoragePoolCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/DeleteVMSnapshotAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/DeleteVMSnapshotCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/DirectNetworkUsageAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/DirectNetworkUsageCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/ExternalNetworkResourceUsageAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/ExternalNetworkResourceUsageCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/FenceAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/FenceCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/GetDomRVersionAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/GetDomRVersionCmd.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/GetFileStatsAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/GetFileStatsCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/GetGPUStatsAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/GetGPUStatsCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/GetHostStatsAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/GetHostStatsCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/GetRouterAlertsAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/GetStorageStatsAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/GetStorageStatsCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/GetVmConfigAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/GetVmConfigCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/GetVmDiskStatsAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/GetVmDiskStatsCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/GetVmIpAddressCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/GetVmStatsAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/GetVmStatsCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/GetVncPortAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/GetVncPortCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/HostStatsEntry.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/MaintainAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/MaintainCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/ManageSnapshotAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/ManageSnapshotCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/MigrateAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/MigrateCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/MigrateWithStorageAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/MigrateWithStorageCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/MigrateWithStorageCompleteAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/MigrateWithStorageCompleteCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/MigrateWithStorageReceiveAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/MigrateWithStorageReceiveCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/MigrateWithStorageSendAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/MigrateWithStorageSendCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/ModifySshKeysCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/ModifyStoragePoolAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/ModifyStoragePoolCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/ModifyVmNicConfigAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/ModifyVmNicConfigCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/NetworkRulesSystemVmCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/NetworkRulesVmSecondaryIpCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/NetworkUsageAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/NetworkUsageCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/PerformanceMonitorAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/PerformanceMonitorCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/PingAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/PingCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/PingRoutingCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/PingRoutingWithNwGroupsCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/PingRoutingWithOvsCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/PingStorageCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/PingTestCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/PlugNicAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/PlugNicCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/PrepareForMigrationAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/PrepareForMigrationCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/PrepareOCFS2NodesCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/PropagateResourceEventCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/ReadyAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/ReadyCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/RebootAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/RebootCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/RebootRouterCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/RecurringNetworkUsageAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/RecurringNetworkUsageCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/RevertToVMSnapshotAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/RevertToVMSnapshotCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/ScaleVmAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/ScaleVmCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/ScheduleHostScanTaskCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/SecStorageFirewallCfgCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/SecStorageSetupAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/SecStorageSetupCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/SecStorageVMSetupCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/SecurityGroupRuleAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/SecurityGroupRulesCmd.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/SetupAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/SetupCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/SetupGuestNetworkCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/ShutdownCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/SnapshotCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/StartAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/StartCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/StartupAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/StartupCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/StartupExternalDhcpCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/StartupExternalFirewallCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/StartupExternalLoadBalancerCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/StartupProxyCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/StartupRoutingCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/StartupSecondaryStorageCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/StartupStorageCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/StartupTrafficMonitorCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/StartupVMMAgentCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/StopAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/StopCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/TransferAgentCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/UnPlugNicAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/UnPlugNicCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/UnregisterNicCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/UnregisterVMCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/UpdateHostPasswordCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/UpgradeSnapshotCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/VMSnapshotBaseCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/VMSnapshotTO.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/ValidateSnapshotAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/ValidateSnapshotCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/VmDiskStatsEntry.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/VmStatsEntry.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/check/CheckSshAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/check/CheckSshCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/proxy/CheckConsoleProxyLoadCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/proxy/ConsoleProxyLoadAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/proxy/ProxyCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/proxy/StartConsoleProxyAgentHttpHandlerCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/proxy/WatchConsoleProxyLoadCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/AggregationControlCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/CreateIpAliasCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/CreateLoadBalancerApplianceCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/DeleteIpAliasCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/DestroyLoadBalancerApplianceCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/DhcpEntryCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/DnsMasqConfigCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/GetRouterAlertsCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/GlobalLoadBalancerConfigAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/GlobalLoadBalancerConfigCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/GroupAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/HealthCheckLBConfigAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/HealthCheckLBConfigCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/IpAliasTO.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/IpAssocAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/IpAssocCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/IpAssocVpcCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/LoadBalancerConfigCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/NetworkElementCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/RemoteAccessVpnCfgCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/SavePasswordCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/SetFirewallRulesAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/SetFirewallRulesCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/SetMonitorServiceCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/SetNetworkACLAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/SetNetworkACLCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/SetPortForwardingRulesAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/SetPortForwardingRulesCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/SetPortForwardingRulesVpcCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/SetSourceNatAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/SetSourceNatCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/SetStaticNatRulesAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/SetStaticNatRulesCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/SetStaticRouteAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/SetStaticRouteCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/Site2SiteVpnCfgCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/SiteLoadBalancerConfig.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/UserDataCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/VmDataCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/routing/VpnUsersCfgCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/AbstractDownloadCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/AbstractUploadCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/CopyVolumeAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/CopyVolumeCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/CreateAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/CreateCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/CreateEntityDownloadURLAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/CreateEntityDownloadURLCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/CreatePrivateTemplateAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/CreatePrivateTemplateCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/DeleteEntityDownloadURLCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/DestroyAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/DestroyCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/DownloadAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/ListTemplateAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/ListTemplateCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/ListVolumeAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/ListVolumeCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/ManageVolumeAvailabilityAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/ManageVolumeAvailabilityCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/MigrateVolumeAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/MigrateVolumeCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/PrimaryStorageDownloadAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/PrimaryStorageDownloadCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/ResizeVolumeAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/ResizeVolumeCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/SsCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/StorageCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/UpgradeDiskAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/UpgradeDiskCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/UploadAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/UploadCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/api/storage/UploadProgressCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/ConfigItem.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/FileConfigItem.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/ScriptConfigItem.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/VRScripts.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/VirtualRouterDeployer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/AbstractConfigItemFacade.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/BumpUpPriorityConfigItem.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/CreateIpAliasConfigItem.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/DeleteIpAliasConfigItem.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/DhcpEntryConfigItem.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/DnsMasqConfigItem.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/IpAssociationConfigItem.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/LoadBalancerConfigItem.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/RemoteAccessVpnConfigItem.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SavePasswordConfigItem.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetFirewallRulesConfigItem.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetGuestNetworkConfigItem.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetMonitorServiceConfigItem.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetNetworkAclConfigItem.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetPortForwardingRulesConfigItem.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetPortForwardingRulesVpcConfigItem.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetSourceNatConfigItem.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetStaticNatRulesConfigItem.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetStaticRouteConfigItem.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/Site2SiteVpnConfigItem.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/VmDataConfigItem.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/VpnUsersConfigItem.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/AclRule.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/AllAclRule.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ConfigBase.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/DhcpConfig.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/DhcpConfigEntry.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/FirewallRule.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/FirewallRules.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ForwardingRule.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ForwardingRules.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/GuestNetwork.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/IcmpAclRule.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/IpAddress.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/IpAddressAlias.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/IpAliases.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/IpAssociation.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/LoadBalancerRule.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/LoadBalancerRules.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/MonitorService.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/NetworkACL.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ProtocolAclRule.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/RemoteAccessVpn.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/Site2SiteVpn.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/StaticNatRule.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/StaticNatRules.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/StaticRoute.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/StaticRoutes.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/TcpAclRule.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/UdpAclRule.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VmData.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VmDhcpConfig.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VmPassword.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VpnUser.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VpnUserList.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/transport/ArrayTypeAdaptor.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/transport/InterfaceTypeAdaptor.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/transport/LoggingExclusionStrategy.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/transport/Request.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/agent/transport/Response.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/exception/UnsupportedVersionException.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/exception/UsageServerException.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/host/HostInfo.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/info/ConsoleProxyConnectionInfo.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/info/ConsoleProxyInfo.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/info/ConsoleProxyStatus.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/info/RunningHostInfoAgregator.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/info/SecStorageVmLoadInfo.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/network/HAProxyConfigurator.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/network/LoadBalancerConfigurator.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/network/LoadBalancerValidator.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/network/resource/CreateLoadBalancerApplianceAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/network/resource/DestroyLoadBalancerApplianceAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/network/resource/TrafficSentinelResource.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/resource/CommandWrapper.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/resource/RequestWrapper.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/resource/ResourceListener.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/resource/ResourceWrapper.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/resource/ServerResource.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/resource/ServerResourceBase.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/resource/hypervisor/HypervisorResource.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/serializer/GsonHelper.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/serializer/SerializerHelper.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/storage/JavaStorageLayer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/storage/StorageLayer.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/storage/resource/StoragePoolResource.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/storage/resource/StorageProcessor.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/storage/resource/StorageSubsystemCommandHandler.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/storage/resource/StorageSubsystemCommandHandlerBase.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/storage/template/FtpTemplateUploader.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/storage/template/IsoProcessor.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/storage/template/LocalTemplateDownloader.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/storage/template/Processor.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/storage/template/QCOW2Processor.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/storage/template/RawImageProcessor.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/storage/template/S3TemplateDownloader.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/storage/template/ScpTemplateDownloader.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/storage/template/TARProcessor.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/storage/template/TemplateConstants.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/storage/template/TemplateDownloader.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/storage/template/TemplateDownloaderBase.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/storage/template/TemplateLocation.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/storage/template/TemplateUploader.java create mode 100644 cosmic-core/nucleo/src/main/java/com/cloud/storage/template/VhdProcessor.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/AttachAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/AttachCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/AttachPrimaryDataStoreAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/AttachPrimaryDataStoreCmd.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/CopyCmdAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/CopyCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/CreateObjectAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/CreateObjectCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/CreatePrimaryDataStoreCmd.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/DeleteCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/DettachAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/DettachCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/DownloadCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/DownloadProgressCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/ForgetObjectCmd.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/IntroduceObjectAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/IntroduceObjectCmd.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/RevertSnapshotCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/SnapshotAndCopyAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/SnapshotAndCopyCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/StorageSubSystemCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/TemplateOrVolumePostUploadCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/UploadStatusAnswer.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/UploadStatusCommand.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/to/ImageStoreTO.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/to/PrimaryDataStoreTO.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/to/SnapshotObjectTO.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/to/TemplateObjectTO.java create mode 100644 cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/to/VolumeObjectTO.java create mode 100644 cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/allocator/module.properties create mode 100644 cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/allocator/spring-core-allocator-context.xml create mode 100644 cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/allocator/spring-core-lifecycle-allocator-context-inheritable.xml create mode 100644 cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/api/module.properties create mode 100644 cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/api/spring-core-lifecycle-api-context-inheritable.xml create mode 100644 cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/backend/module.properties create mode 100644 cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/bootstrap/module.properties create mode 100644 cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/bootstrap/spring-bootstrap-context-inheritable.xml create mode 100644 cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/bootstrap/spring-bootstrap-context.xml create mode 100644 cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/compute/module.properties create mode 100644 cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/compute/spring-core-lifecycle-compute-context-inheritable.xml create mode 100644 cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/core/module.properties create mode 100644 cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/core/spring-core-context.xml create mode 100644 cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/core/spring-core-lifecycle-core-context-inheritable.xml create mode 100644 cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml create mode 100644 cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/discoverer/module.properties create mode 100644 cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/discoverer/spring-core-lifecycle-discoverer-context-inheritable.xml create mode 100644 cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/network/module.properties create mode 100644 cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/network/spring-core-lifecycle-network-context-inheritable.xml create mode 100644 cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/planner/module.properties create mode 100644 cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/planner/spring-core-lifecycle-planner-context-inheritable.xml create mode 100644 cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/storage/module.properties create mode 100644 cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/storage/spring-lifecycle-storage-context-inheritable.xml create mode 100644 cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/system/module.properties create mode 100644 cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/system/spring-core-system-context-inheritable.xml create mode 100644 cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/system/spring-core-system-context.xml create mode 100644 cosmic-core/nucleo/src/test/java/com/cloud/agent/api/routing/SetNetworkACLCommandTest.java create mode 100644 cosmic-core/nucleo/src/test/java/com/cloud/agent/resource/virtualnetwork/ConfigHelperTest.java create mode 100644 cosmic-core/nucleo/src/test/java/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResourceTest.java create mode 100644 cosmic-core/nucleo/src/test/java/com/cloud/agent/transport/RequestTest.java create mode 100644 cosmic-core/nucleo/src/test/java/com/cloud/network/HAProxyConfiguratorTest.java create mode 100644 cosmic-core/nucleo/src/test/java/com/cloud/storage/template/LocalTemplateDownloaderTest.java create mode 100644 cosmic-core/nucleo/src/test/java/com/cloud/storage/template/QCOW2ProcessorTest.java create mode 100644 cosmic-core/nucleo/src/test/java/com/cloud/storage/template/VhdProcessorTest.java create mode 100644 cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/AgentControlAnswerTest.java create mode 100644 cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/AgentControlCommandTest.java create mode 100644 cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/AnswerTest.java create mode 100644 cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/AttachIsoCommandTest.java create mode 100644 cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/BackupSnapshotAnswerTest.java create mode 100644 cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/BackupSnapshotCommandTest.java create mode 100644 cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/BumpUpPriorityCommandTest.java create mode 100644 cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/CancelCommandTest.java create mode 100644 cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/ChangeAgentAnswerTest.java create mode 100644 cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/ChangeAgentCommandTest.java create mode 100644 cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/CheckHealthAnswerTest.java create mode 100644 cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/CheckHealthCommandTest.java create mode 100644 cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/CheckNetworkAnswerTest.java create mode 100644 cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/CheckNetworkCommandTest.java create mode 100644 cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/CheckOnHostCommandTest.java create mode 100644 cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/SnapshotCommandTest.java create mode 100644 cosmic-core/plugins/acl/static-role-based/pom.xml create mode 100644 cosmic-core/plugins/acl/static-role-based/src/main/java/org/apache/cloudstack/acl/StaticRoleBasedAPIAccessChecker.java create mode 100644 cosmic-core/plugins/acl/static-role-based/src/main/resources/META-INF/cloudstack/acl-static-role-based/module.properties create mode 100644 cosmic-core/plugins/acl/static-role-based/src/main/resources/META-INF/cloudstack/acl-static-role-based/spring-acl-static-role-based-context.xml create mode 100644 cosmic-core/plugins/affinity-group-processors/explicit-dedication/pom.xml create mode 100644 cosmic-core/plugins/affinity-group-processors/explicit-dedication/src/main/java/org/apache/cloudstack/affinity/ExplicitDedicationProcessor.java create mode 100644 cosmic-core/plugins/affinity-group-processors/explicit-dedication/src/main/resources/META-INF/cloudstack/explicit-dedication/module.properties create mode 100644 cosmic-core/plugins/affinity-group-processors/explicit-dedication/src/main/resources/META-INF/cloudstack/explicit-dedication/spring-explicit-dedication-context.xml create mode 100644 cosmic-core/plugins/affinity-group-processors/host-anti-affinity/pom.xml create mode 100644 cosmic-core/plugins/affinity-group-processors/host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java create mode 100644 cosmic-core/plugins/affinity-group-processors/host-anti-affinity/src/main/resources/META-INF/cloudstack/host-anti-affinity/module.properties create mode 100644 cosmic-core/plugins/affinity-group-processors/host-anti-affinity/src/main/resources/META-INF/cloudstack/host-anti-affinity/spring-host-anti-affinity-context.xml create mode 100644 cosmic-core/plugins/api/discovery/pom.xml create mode 100644 cosmic-core/plugins/api/discovery/src/main/java/org/apache/cloudstack/api/command/user/discovery/ListApisCmd.java create mode 100644 cosmic-core/plugins/api/discovery/src/main/java/org/apache/cloudstack/api/response/ApiDiscoveryResponse.java create mode 100644 cosmic-core/plugins/api/discovery/src/main/java/org/apache/cloudstack/api/response/ApiParameterResponse.java create mode 100644 cosmic-core/plugins/api/discovery/src/main/java/org/apache/cloudstack/api/response/ApiResponseResponse.java create mode 100644 cosmic-core/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryService.java create mode 100644 cosmic-core/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryServiceImpl.java create mode 100644 cosmic-core/plugins/api/discovery/src/test/java/org/apache/cloudstack/discovery/ApiDiscoveryTest.java create mode 100644 cosmic-core/plugins/api/rate-limit/pom.xml create mode 100644 cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/api/command/admin/ratelimit/ResetApiLimitCmd.java create mode 100644 cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/api/command/user/ratelimit/GetApiLimitCmd.java create mode 100644 cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/api/response/ApiLimitResponse.java create mode 100644 cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/ApiRateLimitService.java create mode 100644 cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/ApiRateLimitServiceImpl.java create mode 100644 cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/EhcacheLimitStore.java create mode 100644 cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/LimitStore.java create mode 100644 cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/StoreEntry.java create mode 100644 cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/StoreEntryImpl.java create mode 100644 cosmic-core/plugins/api/rate-limit/src/main/resources/META-INF/cloudstack/rate-limit/module.properties create mode 100644 cosmic-core/plugins/api/rate-limit/src/main/resources/META-INF/cloudstack/rate-limit/spring-rate-limit-context.xml create mode 100644 cosmic-core/plugins/api/rate-limit/src/test/java/org/apache/cloudstack/ratelimit/ApiRateLimitTest.java create mode 100644 cosmic-core/plugins/api/rate-limit/src/test/java/org/apache/cloudstack/ratelimit/integration/APITest.java create mode 100644 cosmic-core/plugins/api/rate-limit/src/test/java/org/apache/cloudstack/ratelimit/integration/LoginResponse.java create mode 100644 cosmic-core/plugins/api/rate-limit/src/test/java/org/apache/cloudstack/ratelimit/integration/RateLimitIntegrationTest.java create mode 100644 cosmic-core/plugins/dedicated-resources/pom.xml create mode 100644 cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicateClusterCmd.java create mode 100644 cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicateHostCmd.java create mode 100644 cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicatePodCmd.java create mode 100644 cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicateZoneCmd.java create mode 100644 cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedClustersCmd.java create mode 100644 cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedHostsCmd.java create mode 100644 cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedPodsCmd.java create mode 100644 cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedZonesCmd.java create mode 100644 cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedClusterCmd.java create mode 100644 cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedHostCmd.java create mode 100644 cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedPodCmd.java create mode 100644 cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedZoneCmd.java create mode 100644 cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/response/DedicateClusterResponse.java create mode 100644 cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/response/DedicateHostResponse.java create mode 100644 cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/response/DedicatePodResponse.java create mode 100644 cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/response/DedicateZoneResponse.java create mode 100644 cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/dedicated/DedicatedResourceManagerImpl.java create mode 100644 cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/dedicated/DedicatedService.java create mode 100644 cosmic-core/plugins/dedicated-resources/src/main/resources/META-INF/cloudstack/core/spring-dedicated-resources-core-context.xml create mode 100644 cosmic-core/plugins/dedicated-resources/src/test/java/org/apache/cloudstack/dedicated/manager/DedicatedApiUnitTest.java create mode 100644 cosmic-core/plugins/dedicated-resources/src/test/resource/dedicatedContext.xml create mode 100644 cosmic-core/plugins/deployment-planners/implicit-dedication/pom.xml create mode 100644 cosmic-core/plugins/deployment-planners/implicit-dedication/src/main/java/com/cloud/deploy/ImplicitDedicationPlanner.java create mode 100644 cosmic-core/plugins/deployment-planners/implicit-dedication/src/main/java/resources/META-INF/cloudstack/implicit-dedication/module.properties create mode 100644 cosmic-core/plugins/deployment-planners/implicit-dedication/src/main/java/resources/META-INF/cloudstack/implicit-dedication/spring-implicit-dedication-context.xml create mode 100644 cosmic-core/plugins/deployment-planners/implicit-dedication/src/test/java/org/apache/cloudstack/implicitplanner/ImplicitPlannerTest.java create mode 100644 cosmic-core/plugins/deployment-planners/user-concentrated-pod/pom.xml create mode 100644 cosmic-core/plugins/deployment-planners/user-concentrated-pod/src/main/java/com/cloud/deploy/UserConcentratedPodPlanner.java create mode 100644 cosmic-core/plugins/deployment-planners/user-concentrated-pod/src/main/resources/META-INF/cloudstack/user-concentrated-pod/module.properties create mode 100644 cosmic-core/plugins/deployment-planners/user-concentrated-pod/src/main/resources/META-INF/cloudstack/user-concentrated-pod/spring-user-concentrated-pod-context.xml create mode 100644 cosmic-core/plugins/deployment-planners/user-dispersing/pom.xml create mode 100644 cosmic-core/plugins/deployment-planners/user-dispersing/src/main/java/com/cloud/deploy/UserDispersingPlanner.java create mode 100644 cosmic-core/plugins/ha-planners/skip-heurestics/pom.xml create mode 100644 cosmic-core/plugins/ha-planners/skip-heurestics/src/main/java/com/cloud/deploy/SkipHeuresticsPlanner.java create mode 100644 cosmic-core/plugins/ha-planners/skip-heurestics/src/main/resources/META-INF/cloudstack/skip-heurestics/module.properties create mode 100644 cosmic-core/plugins/ha-planners/skip-heurestics/src/main/resources/META-INF/cloudstack/skip-heurestics/spring-skip-heurestics-context.xml create mode 100644 cosmic-core/plugins/host-allocators/random/pom.xml create mode 100644 cosmic-core/plugins/host-allocators/random/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java create mode 100644 cosmic-core/plugins/host-allocators/random/src/main/resources/META-INF/cloudstack/host-allocator-random/module.properties create mode 100644 cosmic-core/plugins/host-allocators/random/src/main/resources/META-INF/cloudstack/host-allocator-random/spring-host-allocator-random-context.xml create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/database/BaremetalDhcpDaoImpl.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/database/BaremetalPxeDaoImpl.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/database/BaremetalRctVO.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalDiscoverer.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalGuru.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalPlanner.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalTemplateAdapter.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BaremetalManager.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BaremetalManagerImpl.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BaremetalVlanManager.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BaremetalVlanManagerImpl.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BareMetalPingServiceImpl.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BareMetalResourceBase.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetaNetworkGuru.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpElement.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpManager.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpManagerImpl.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpResourceBase.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpResponse.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpdResource.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDnsmasqResource.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalKickStartPxeResource.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalKickStartServiceImpl.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPingPxeResource.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeElement.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeKickStartResponse.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeManager.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeManagerImpl.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxePingResponse.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeResourceBase.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeResponse.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeService.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalRctResponse.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/Force10BaremetalSwitchBackend.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/SecurityGroupHttpClient.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/schema/SecurityGroupRule.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/schema/SecurityGroupVmRuleSet.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalDhcpCmd.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalHostCmd.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalPxeCmd.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalRctCmd.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/BaremetalProvisionDoneNotificationCmd.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/DeleteBaremetalRctCmd.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/ListBaremetalDhcpCmd.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/ListBaremetalPxeServersCmd.java create mode 100644 cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/ListBaremetalRctCmd.java create mode 100644 cosmic-core/plugins/network-elements/internal-loadbalancer/pom.xml create mode 100644 cosmic-core/plugins/network-elements/internal-loadbalancer/src/main/java/org/apache/cloudstack/network/element/InternalLoadBalancerElement.java create mode 100644 cosmic-core/plugins/network-elements/internal-loadbalancer/src/main/java/org/apache/cloudstack/network/lb/InternalLoadBalancerVMManager.java create mode 100644 cosmic-core/plugins/network-elements/internal-loadbalancer/src/main/java/org/apache/cloudstack/network/lb/InternalLoadBalancerVMManagerImpl.java create mode 100644 cosmic-core/plugins/network-elements/internal-loadbalancer/src/main/resources/META-INF/cloudstack/core/spring-internallb-core-context.xml create mode 100644 cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbelement/ElementChildTestConfiguration.java create mode 100644 cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbelement/InternalLbElementServiceTest.java create mode 100644 cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbelement/InternalLbElementTest.java create mode 100644 cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbvmmgr/InternalLBVMManagerTest.java create mode 100644 cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbvmmgr/InternalLBVMServiceTest.java create mode 100644 cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbvmmgr/LbChildTestConfiguration.java create mode 100644 cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/resources/lb_element.xml create mode 100644 cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/resources/lb_mgr.xml create mode 100644 cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/resources/lb_svc.xml create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/pom.xml create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/ConfigurePortForwardingRulesOnLogicalRouterAnswer.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/ConfigurePortForwardingRulesOnLogicalRouterCommand.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/ConfigurePublicIpsOnLogicalRouterAnswer.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/ConfigurePublicIpsOnLogicalRouterCommand.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/ConfigureStaticNatRulesOnLogicalRouterAnswer.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/ConfigureStaticNatRulesOnLogicalRouterCommand.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/CreateLogicalRouterAnswer.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/CreateLogicalRouterCommand.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/CreateLogicalSwitchAnswer.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/CreateLogicalSwitchCommand.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/CreateLogicalSwitchPortAnswer.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/CreateLogicalSwitchPortCommand.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/DeleteLogicalRouterAnswer.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/DeleteLogicalRouterCommand.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/DeleteLogicalSwitchAnswer.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/DeleteLogicalSwitchCommand.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/DeleteLogicalSwitchPortAnswer.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/DeleteLogicalSwitchPortCommand.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/FindLogicalSwitchAnswer.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/FindLogicalSwitchCommand.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/FindLogicalSwitchPortAnswer.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/FindLogicalSwitchPortCommand.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/StartupNiciraNvpCommand.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/UpdateLogicalSwitchPortAnswer.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/UpdateLogicalSwitchPortCommand.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/api/commands/AddNiciraNvpDeviceCmd.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/api/commands/DeleteNiciraNvpDeviceCmd.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/api/commands/ListNiciraNvpDeviceNetworksCmd.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/api/commands/ListNiciraNvpDevicesCmd.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/api/response/NiciraNvpDeviceResponse.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/NiciraNvpDeviceVO.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/NiciraNvpNicMappingVO.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/NiciraNvpRouterMappingVO.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/dao/NiciraNvpDao.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/dao/NiciraNvpDaoImpl.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/dao/NiciraNvpNicMappingDao.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/dao/NiciraNvpNicMappingDaoImpl.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/dao/NiciraNvpRouterMappingDao.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/dao/NiciraNvpRouterMappingDaoImpl.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/element/NiciraNvpElement.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/element/NiciraNvpElementService.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/guru/NiciraNvpGuestNetworkGuru.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/AccessConfiguration.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/AccessRule.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/Acl.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/AclRule.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/Attachment.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/BaseNiciraEntity.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/BaseNiciraNamedEntity.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/ControlClusterStatus.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/DestinationNatRule.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/ExecutionCounter.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/L3GatewayAttachment.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/LogicalRouter.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/LogicalRouterPort.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/LogicalSwitch.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/LogicalSwitchPort.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/Match.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NatRule.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NatRuleAdapter.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraConstants.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraNvpApi.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraNvpApiException.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraNvpList.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraNvpTag.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraRestClient.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/PatchAttachment.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/RouterNextHop.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/RoutingConfig.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/RoutingConfigAdapter.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/RoutingTableRoutingConfig.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/SecurityProfile.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/SecurityRule.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/SingleDefaultRouteImplicitRoutingConfig.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/SourceNatRule.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/TransportZoneBinding.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/VifAttachment.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/NiciraNvpRequestWrapper.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/NiciraNvpResource.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/NiciraNvpUtilities.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraCheckHealthCommandWrapper.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpConfigurePortForwardingRulesCommandWrapper.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpConfigurePublicIpsCommandWrapper.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpConfigureStaticNatRulesCommandWrapper.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpCreateLogicalRouterCommandWrapper.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpCreateLogicalSwitchCommandWrapper.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpCreateLogicalSwitchPortCommandWrapper.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpDeleteLogicalRouterCommandWrapper.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpDeleteLogicalSwitchCommandWrapper.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpDeleteLogicalSwitchPortCommandWrapper.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpFindLogicalSwitchCommandWrapper.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpFindLogicalSwitchPortCommandWrapper.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpMaintainCommandWrapper.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpReadyCommandWrapper.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpUpdateLogicalSwitchPortCommandWrapper.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/utils/CommandRetryUtility.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/resources/META-INF/cloudstack/nvp/module.properties create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/main/resources/META-INF/cloudstack/nvp/spring-nvp-context.xml create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/element/NiciraNvpElementTest.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/guru/NiciraNvpGuestNetworkGuruTest.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/ExecutionCounterTest.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NatRuleAdapterTest.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NatRuleTest.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NiciraNvpApiIT.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NiciraNvpApiTest.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NiciraRestClientTest.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NiciraTagTest.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/RoutingConfigAdapterTest.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/resource/NiciraNvpRequestWrapperTest.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/resource/NiciraNvpResourceTest.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/resource/wrapper/NiciraCheckHealthCommandWrapperTest.java create mode 100644 cosmic-core/plugins/network-elements/nicira-nvp/src/test/resources/config.properties create mode 100755 cosmic-core/plugins/pom.xml create mode 100644 cosmic-core/plugins/storage-allocators/random/pom.xml create mode 100644 cosmic-core/plugins/storage-allocators/random/src/main/java/org/apache/cloudstack/storage/allocator/RandomStoragePoolAllocator.java create mode 100644 cosmic-core/plugins/storage/image/default/pom.xml create mode 100644 cosmic-core/plugins/storage/image/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackImageStoreDriverImpl.java create mode 100644 cosmic-core/plugins/storage/image/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackImageStoreLifeCycleImpl.java create mode 100644 cosmic-core/plugins/storage/image/default/src/main/java/org/apache/cloudstack/storage/datastore/provider/CloudStackImageStoreProviderImpl.java create mode 100644 cosmic-core/plugins/storage/image/default/src/main/resources/META-INF/cloudstack/storage-image-default/module.properties create mode 100644 cosmic-core/plugins/storage/image/default/src/main/resources/META-INF/cloudstack/storage-image-default/spring-storage-image-default-context.xml create mode 100644 cosmic-core/plugins/storage/image/s3/pom.xml create mode 100644 cosmic-core/plugins/storage/image/s3/src/main/java/org/apache/cloudstack/storage/datastore/driver/S3ImageStoreDriverImpl.java create mode 100644 cosmic-core/plugins/storage/image/s3/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/S3ImageStoreLifeCycleImpl.java create mode 100644 cosmic-core/plugins/storage/image/s3/src/main/java/org/apache/cloudstack/storage/datastore/provider/S3ImageStoreProviderImpl.java create mode 100644 cosmic-core/plugins/storage/image/s3/src/main/resources/META-INF/cloudstack/storage-image-s3/module.properties create mode 100644 cosmic-core/plugins/storage/image/s3/src/main/resources/META-INF/cloudstack/storage-image-s3/spring-storage-image-s3-context.xml create mode 100644 cosmic-core/plugins/storage/volume/default/pom.xml create mode 100644 cosmic-core/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java create mode 100644 cosmic-core/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImpl.java create mode 100644 cosmic-core/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/provider/CloudStackPrimaryDataStoreProviderImpl.java create mode 100644 cosmic-core/plugins/storage/volume/default/src/main/resources/META-INF/cloudstack/storage-volume-default/module.properties create mode 100644 cosmic-core/plugins/storage/volume/default/src/main/resources/META-INF/cloudstack/storage-volume-default/spring-storage-volume-default-context.xml create mode 100644 cosmic-core/pom.xml create mode 100755 cosmic-core/python/bindir/cloud-external-ipallocator.py create mode 100755 cosmic-core/python/bindir/cloud-grab-dependent-library-versions create mode 100755 cosmic-core/python/distro/centos/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in create mode 100755 cosmic-core/python/distro/fedora/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in create mode 100755 cosmic-core/python/distro/opensuse/SYSCONFDIR/init.d/cloud-ipallocator.in create mode 100644 cosmic-core/python/distro/rhel/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in create mode 100755 cosmic-core/python/distro/sles/SYSCONFDIR/init.d/cloud-ipallocator.in create mode 100755 cosmic-core/python/distro/ubuntu/SYSCONFDIR/init.d/cloud-ipallocator.in create mode 100755 cosmic-core/python/incubation/cloud-web-ipallocator.py create mode 100644 cosmic-core/python/lib/cloud_utils.py create mode 100644 cosmic-core/python/lib/cloudutils/__init__.py create mode 100644 cosmic-core/python/lib/cloudutils/cloudException.py create mode 100644 cosmic-core/python/lib/cloudutils/configFileOps.py create mode 100644 cosmic-core/python/lib/cloudutils/db.py create mode 100644 cosmic-core/python/lib/cloudutils/globalEnv.py create mode 100644 cosmic-core/python/lib/cloudutils/networkConfig.py create mode 100755 cosmic-core/python/lib/cloudutils/serviceConfig.py create mode 100644 cosmic-core/python/lib/cloudutils/serviceConfigServer.py create mode 100755 cosmic-core/python/lib/cloudutils/syscfg.py create mode 100755 cosmic-core/python/lib/cloudutils/utilities.py create mode 100644 cosmic-core/scripts/common/keys/ssl-keys.py create mode 100755 cosmic-core/scripts/installer/createtmplt.sh create mode 100755 cosmic-core/scripts/installer/createvolume.sh create mode 100755 cosmic-core/scripts/installer/installcentos.sh create mode 100755 cosmic-core/scripts/installer/installdomp.sh create mode 100755 cosmic-core/scripts/installer/run_installer.sh create mode 100755 cosmic-core/scripts/network/domr/router_proxy.sh create mode 100644 cosmic-core/scripts/network/exdhcp/dhcpd_edithosts.py create mode 100755 cosmic-core/scripts/network/exdhcp/dnsmasq_edithosts.sh create mode 100755 cosmic-core/scripts/network/exdhcp/prepare_dhcpd.sh create mode 100755 cosmic-core/scripts/network/exdhcp/prepare_dnsmasq.sh create mode 100644 cosmic-core/scripts/network/juniper/access-profile-add.xml create mode 100644 cosmic-core/scripts/network/juniper/access-profile-getall.xml create mode 100644 cosmic-core/scripts/network/juniper/access-profile-getone.xml create mode 100644 cosmic-core/scripts/network/juniper/address-book-entry-add.xml create mode 100644 cosmic-core/scripts/network/juniper/address-book-entry-getall.xml create mode 100644 cosmic-core/scripts/network/juniper/address-book-entry-getone.xml create mode 100644 cosmic-core/scripts/network/juniper/address-pool-add.xml create mode 100644 cosmic-core/scripts/network/juniper/address-pool-getall.xml create mode 100644 cosmic-core/scripts/network/juniper/address-pool-getone.xml create mode 100644 cosmic-core/scripts/network/juniper/application-add.xml create mode 100644 cosmic-core/scripts/network/juniper/application-getone.xml create mode 100644 cosmic-core/scripts/network/juniper/close-configuration.xml create mode 100644 cosmic-core/scripts/network/juniper/commit.xml create mode 100644 cosmic-core/scripts/network/juniper/dest-nat-pool-add.xml create mode 100644 cosmic-core/scripts/network/juniper/dest-nat-pool-getall.xml create mode 100644 cosmic-core/scripts/network/juniper/dest-nat-pool-getone.xml create mode 100644 cosmic-core/scripts/network/juniper/dest-nat-rule-add.xml create mode 100644 cosmic-core/scripts/network/juniper/dest-nat-rule-getall.xml create mode 100644 cosmic-core/scripts/network/juniper/dest-nat-rule-getone.xml create mode 100644 cosmic-core/scripts/network/juniper/dynamic-vpn-client-add.xml create mode 100644 cosmic-core/scripts/network/juniper/dynamic-vpn-client-getall.xml create mode 100644 cosmic-core/scripts/network/juniper/dynamic-vpn-client-getone.xml create mode 100644 cosmic-core/scripts/network/juniper/filter-getone.xml create mode 100644 cosmic-core/scripts/network/juniper/filter-term-getone.xml create mode 100644 cosmic-core/scripts/network/juniper/firewall-filter-bytes-getall.xml create mode 100644 cosmic-core/scripts/network/juniper/firewall-filter-term-add.xml create mode 100644 cosmic-core/scripts/network/juniper/firewall-filter-term-getone.xml create mode 100644 cosmic-core/scripts/network/juniper/guest-vlan-filter-term-add.xml create mode 100644 cosmic-core/scripts/network/juniper/ike-gateway-add.xml create mode 100644 cosmic-core/scripts/network/juniper/ike-gateway-getall.xml create mode 100644 cosmic-core/scripts/network/juniper/ike-gateway-getone.xml create mode 100644 cosmic-core/scripts/network/juniper/ike-policy-add.xml create mode 100644 cosmic-core/scripts/network/juniper/ike-policy-getall.xml create mode 100644 cosmic-core/scripts/network/juniper/ike-policy-getone.xml create mode 100644 cosmic-core/scripts/network/juniper/ipsec-vpn-add.xml create mode 100644 cosmic-core/scripts/network/juniper/ipsec-vpn-getall.xml create mode 100644 cosmic-core/scripts/network/juniper/ipsec-vpn-getone.xml create mode 100644 cosmic-core/scripts/network/juniper/login.xml create mode 100644 cosmic-core/scripts/network/juniper/open-configuration.xml create mode 100644 cosmic-core/scripts/network/juniper/private-interface-add.xml create mode 100644 cosmic-core/scripts/network/juniper/private-interface-getall.xml create mode 100644 cosmic-core/scripts/network/juniper/private-interface-getone.xml create mode 100644 cosmic-core/scripts/network/juniper/private-interface-with-filters-add.xml create mode 100644 cosmic-core/scripts/network/juniper/proxy-arp-add.xml create mode 100644 cosmic-core/scripts/network/juniper/proxy-arp-getall.xml create mode 100644 cosmic-core/scripts/network/juniper/proxy-arp-getone.xml create mode 100644 cosmic-core/scripts/network/juniper/public-ip-filter-term-add.xml create mode 100644 cosmic-core/scripts/network/juniper/rollback.xml create mode 100644 cosmic-core/scripts/network/juniper/security-policy-add.xml create mode 100644 cosmic-core/scripts/network/juniper/security-policy-getall.xml create mode 100644 cosmic-core/scripts/network/juniper/security-policy-getone.xml create mode 100644 cosmic-core/scripts/network/juniper/security-policy-group.xml create mode 100644 cosmic-core/scripts/network/juniper/security-policy-rename.xml create mode 100644 cosmic-core/scripts/network/juniper/src-nat-pool-add.xml create mode 100644 cosmic-core/scripts/network/juniper/src-nat-pool-getone.xml create mode 100644 cosmic-core/scripts/network/juniper/src-nat-rule-add.xml create mode 100644 cosmic-core/scripts/network/juniper/src-nat-rule-getall.xml create mode 100644 cosmic-core/scripts/network/juniper/src-nat-rule-getone.xml create mode 100644 cosmic-core/scripts/network/juniper/static-nat-rule-add.xml create mode 100644 cosmic-core/scripts/network/juniper/static-nat-rule-getall.xml create mode 100644 cosmic-core/scripts/network/juniper/static-nat-rule-getone.xml create mode 100644 cosmic-core/scripts/network/juniper/template-entry.xml create mode 100644 cosmic-core/scripts/network/juniper/test.xml create mode 100644 cosmic-core/scripts/network/juniper/zone-interface-add.xml create mode 100644 cosmic-core/scripts/network/juniper/zone-interface-getone.xml create mode 100755 cosmic-core/scripts/network/ping/baremetal_user_data.py create mode 100755 cosmic-core/scripts/network/ping/prepare_kickstart_kernel_initrd.py create mode 100755 cosmic-core/scripts/storage/checkchildren.sh create mode 100755 cosmic-core/scripts/storage/installIso.sh create mode 100755 cosmic-core/scripts/storage/qcow2/create_private_template.sh create mode 100755 cosmic-core/scripts/storage/qcow2/createtmplt.sh create mode 100755 cosmic-core/scripts/storage/qcow2/createvm.sh create mode 100755 cosmic-core/scripts/storage/qcow2/createvolume.sh create mode 100755 cosmic-core/scripts/storage/qcow2/delvm.sh create mode 100755 cosmic-core/scripts/storage/qcow2/get_domr_kernel.sh create mode 100755 cosmic-core/scripts/storage/qcow2/get_iqn.sh create mode 100755 cosmic-core/scripts/storage/qcow2/importmpl.sh create mode 100755 cosmic-core/scripts/storage/qcow2/listvmdisk.sh create mode 100755 cosmic-core/scripts/storage/qcow2/listvmdisksize.sh create mode 100755 cosmic-core/scripts/storage/qcow2/listvmtmplt.sh create mode 100755 cosmic-core/scripts/storage/qcow2/listvolume.sh create mode 100755 cosmic-core/scripts/storage/qcow2/managesnapshot.sh create mode 100755 cosmic-core/scripts/storage/qcow2/managevolume.sh create mode 100755 cosmic-core/scripts/storage/qcow2/resizevolume.sh create mode 100755 cosmic-core/scripts/storage/secondary/cloud-install-sys-tmplt create mode 100644 cosmic-core/scripts/storage/secondary/cloud-install-sys-tmplt.py create mode 100755 cosmic-core/scripts/storage/secondary/create_privatetemplate_from_snapshot_xen.sh create mode 100755 cosmic-core/scripts/storage/secondary/createtmplt.sh create mode 100755 cosmic-core/scripts/storage/secondary/createvolume.sh create mode 100755 cosmic-core/scripts/storage/secondary/installIso.sh create mode 100755 cosmic-core/scripts/storage/secondary/listvmtmplt.sh create mode 100755 cosmic-core/scripts/storage/secondary/listvolume.sh create mode 100755 cosmic-core/scripts/storage/secondary/swift create mode 100755 cosmic-core/scripts/util/ipmi.py create mode 100755 cosmic-core/scripts/util/macgen.py create mode 100755 cosmic-core/scripts/util/prepare_linmin.sh create mode 100755 cosmic-core/scripts/util/qemu-ifup create mode 100755 cosmic-core/scripts/util/qemu-ivs-ifup create mode 100755 cosmic-core/scripts/vm/hypervisor/kvm/kvmheartbeat.sh create mode 100755 cosmic-core/scripts/vm/hypervisor/kvm/patchviasocket.pl create mode 100755 cosmic-core/scripts/vm/hypervisor/kvm/setup_agent.sh create mode 100644 cosmic-core/scripts/vm/hypervisor/ovm3/cloudstack.py create mode 100755 cosmic-core/scripts/vm/hypervisor/ovm3/storagehealth.py create mode 100755 cosmic-core/scripts/vm/hypervisor/update_host_passwd.sh create mode 100755 cosmic-core/scripts/vm/hypervisor/versions.sh create mode 100644 cosmic-core/scripts/vm/hypervisor/xenserver/add_to_vcpus_params_live.sh create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/check_heartbeat.sh create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/cloud-clean-vlan.sh create mode 100644 cosmic-core/scripts/vm/hypervisor/xenserver/cloud-plugin-storage create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/cloud-prepare-upgrade.sh create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/cloud-propagate-vlan.sh create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/cloud-setup-bonding.sh create mode 100644 cosmic-core/scripts/vm/hypervisor/xenserver/cloudlog create mode 100644 cosmic-core/scripts/vm/hypervisor/xenserver/cloudstack_pluginlib.py create mode 100644 cosmic-core/scripts/vm/hypervisor/xenserver/cloudstack_plugins.conf create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/copy_vhd_from_secondarystorage.sh create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/copy_vhd_to_secondarystorage.sh create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/create_privatetemplate_from_snapshot.sh create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/kill_copy_process.sh create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/launch_hb.sh create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/make_migratable.sh create mode 100644 cosmic-core/scripts/vm/hypervisor/xenserver/mockxcpplugin.py create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/network_info.sh create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/ovs-get-bridge.sh create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/ovs-get-dhcp-iface.sh create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/ovs-pvlan create mode 100644 cosmic-core/scripts/vm/hypervisor/xenserver/ovs-vif-flows.py create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/ovstunnel create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/perfmon.py create mode 100644 cosmic-core/scripts/vm/hypervisor/xenserver/s3xenserver create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/setup_heartbeat_file.sh create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/setup_heartbeat_sr.sh create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/setup_iscsi.sh create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/setupxenserver.sh create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/storagePlugin create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/swift create mode 100644 cosmic-core/scripts/vm/hypervisor/xenserver/swiftxenserver create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/upgrade_snapshot.sh create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/upgrade_vnc_config.sh create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/vmops create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/vmopsSnapshot create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/vmopspremium create mode 100644 cosmic-core/scripts/vm/hypervisor/xenserver/xcposs/NFSSR.py create mode 100644 cosmic-core/scripts/vm/hypervisor/xenserver/xcposs/patch create mode 100644 cosmic-core/scripts/vm/hypervisor/xenserver/xcposs/vmops create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/xcpserver/NFSSR.py create mode 100644 cosmic-core/scripts/vm/hypervisor/xenserver/xcpserver/patch create mode 100644 cosmic-core/scripts/vm/hypervisor/xenserver/xen-ovs-vif-flows.rules create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/xenheartbeat.sh create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/xenserver56/InterfaceReconfigure.py create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/xenserver56/NFSSR.py create mode 100644 cosmic-core/scripts/vm/hypervisor/xenserver/xenserver56/patch create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/xenserver56fp1/NFSSR.py create mode 100644 cosmic-core/scripts/vm/hypervisor/xenserver/xenserver56fp1/patch create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/xenserver60/NFSSR.py create mode 100644 cosmic-core/scripts/vm/hypervisor/xenserver/xenserver60/patch create mode 100644 cosmic-core/scripts/vm/hypervisor/xenserver/xenserver62/patch create mode 100644 cosmic-core/scripts/vm/hypervisor/xenserver/xenserver65/patch create mode 100755 cosmic-core/scripts/vm/hypervisor/xenserver/xs_cleanup.sh create mode 100755 cosmic-core/scripts/vm/network/ovs-pvlan-cleanup.sh create mode 100755 cosmic-core/scripts/vm/network/ovs-pvlan-dhcp-host.sh create mode 100755 cosmic-core/scripts/vm/network/ovs-pvlan-vm.sh create mode 100755 cosmic-core/scripts/vm/network/security_group.py create mode 100755 cosmic-core/scripts/vm/network/vnet/cloudstack_pluginlib.py create mode 100755 cosmic-core/scripts/vm/network/vnet/modifyvlan.sh create mode 100755 cosmic-core/scripts/vm/network/vnet/modifyvxlan.sh create mode 100755 cosmic-core/scripts/vm/network/vnet/ovstunnel.py create mode 100755 cosmic-core/scripts/vm/pingtest.sh create mode 100644 cosmic-core/scripts/vm/systemvm/id_rsa.cloud create mode 100644 cosmic-core/scripts/vm/systemvm/injectkeys.py create mode 100755 cosmic-core/scripts/vm/systemvm/injectkeys.sh create mode 100644 cosmic-core/server/conf/cloudstack-limits.conf.in create mode 100644 cosmic-core/server/conf/cloudstack-sudoers.in create mode 100755 cosmic-core/server/conf/log4j-cloud.xml.in create mode 100644 cosmic-core/server/conf/migration-components.xml create mode 100644 cosmic-core/server/pom.xml create mode 100755 cosmic-core/server/scripts/vmops-fix-mysql-config create mode 100644 cosmic-core/server/src/main/java/com/cloud/account/SecurityManager.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/acl/AffinityGroupAccessChecker.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/acl/DomainChecker.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/agent/manager/allocator/impl/FirstFitRoutingAllocator.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/agent/manager/allocator/impl/RecreateHostAllocator.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/agent/manager/allocator/impl/TestingAllocator.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/agent/manager/allocator/impl/UserConcentratedAllocator.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/agent/manager/authn/AgentAuthnException.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/agent/manager/authn/AgentAuthorizer.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/agent/manager/authn/impl/BasicAgentAuthManager.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/alert/AlertManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/alert/ClusterAlertAdapter.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/alert/ConsoleProxyAlertAdapter.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/alert/SecondaryStorageVmAlertAdapter.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/ApiAsyncJobDispatcher.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/ApiDBUtils.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/ApiDispatcher.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/ApiGsonHelper.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/ApiResponseGsonHelper.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/ApiResponseHelper.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/ApiSerializerHelper.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/ApiServer.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/ApiServlet.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/EncodedStringTypeAdapter.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/ResponseObjectTypeAdapter.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/SerializationContext.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/StringMapTypeAdapter.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/auth/APIAuthenticationManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/auth/DefaultLoginAPIAuthenticatorCmd.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/auth/DefaultLogoutAPIAuthenticatorCmd.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/dispatch/CommandCreationWorker.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/dispatch/DispatchChain.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/dispatch/DispatchChainFactory.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/dispatch/DispatchTask.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/dispatch/DispatchWorker.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/dispatch/ParamGenericValidationWorker.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/dispatch/ParamUnpackWorker.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/dispatch/SpecificCmdValidationWorker.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/doc/Alert.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/doc/ApiXmlDocReader.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/doc/ApiXmlDocWriter.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/doc/Argument.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/doc/Command.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/AccountJoinDao.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/AccountJoinDaoImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/AffinityGroupJoinDao.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/AffinityGroupJoinDaoImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/AsyncJobJoinDao.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/AsyncJobJoinDaoImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDao.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDaoImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDao.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/DomainJoinDao.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/DomainJoinDaoImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDao.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/HostJoinDao.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/HostJoinDaoImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/HostTagDao.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/HostTagDaoImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/ImageStoreJoinDao.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/ImageStoreJoinDaoImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/InstanceGroupJoinDao.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/InstanceGroupJoinDaoImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/ProjectAccountJoinDao.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/ProjectAccountJoinDaoImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/ProjectInvitationJoinDao.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/ProjectInvitationJoinDaoImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/ProjectJoinDao.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/ProjectJoinDaoImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/ResourceTagJoinDao.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/ResourceTagJoinDaoImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/SecurityGroupJoinDao.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/SecurityGroupJoinDaoImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDao.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDao.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/StorageTagDao.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/StorageTagDaoImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDao.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/UserAccountJoinDao.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/UserAccountJoinDaoImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDao.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/VolumeJoinDao.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/dao/VolumeJoinDaoImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/vo/AccountJoinVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/vo/AffinityGroupJoinVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/vo/AsyncJobJoinVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/vo/BaseViewVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/vo/ControlledViewEntity.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/vo/DataCenterJoinVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/vo/DiskOfferingJoinVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/vo/DomainJoinVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/vo/DomainRouterJoinVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/vo/EventJoinVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/vo/HostJoinVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/vo/HostTagVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/vo/ImageStoreJoinVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/vo/InstanceGroupJoinVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/vo/ProjectAccountJoinVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/vo/ProjectInvitationJoinVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/vo/ProjectJoinVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/vo/ResourceTagJoinVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/vo/SecurityGroupJoinVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/vo/ServiceOfferingJoinVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/vo/StoragePoolJoinVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/vo/StorageTagVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/vo/TemplateJoinVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/vo/UserAccountJoinVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/query/vo/VolumeJoinVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/response/ApiResponseSerializer.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/response/EmptyFieldExclusionStrategy.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/response/SecurityGroupResultObject.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/api/response/SecurityGroupRuleResultObject.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/async/AsyncJobResult.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/capacity/CapacityManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/capacity/ComputeCapacityListener.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/capacity/StorageCapacityListener.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/configuration/Config.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/configuration/ZoneConfig.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/consoleproxy/AgentBasedConsoleProxyManager.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/consoleproxy/AgentBasedStandaloneConsoleProxyManager.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/consoleproxy/AgentHook.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/consoleproxy/AgentHookBase.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyAlertEventArgs.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyBalanceAllocator.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyListener.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagementState.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManager.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyService.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/consoleproxy/StaticConsoleProxyManager.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/dc/DedicatedResourceVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/dc/dao/DedicatedResourceDao.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/dc/dao/DedicatedResourceDaoImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/deploy/PlannerHostReservationVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/deploy/dao/PlannerHostReservationDao.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/deploy/dao/PlannerHostReservationDaoImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/event/ActionEventInterceptor.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/event/ActionEventUtils.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/event/AlertGenerator.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/event/dao/EventJoinDao.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/event/dao/EventJoinDaoImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/ha/AbstractInvestigatorImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/ha/CheckOnAgentInvestigator.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/ha/HaWorkVO.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/ha/HighAvailabilityManagerExtImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/ha/HighAvailabilityManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/ha/KVMFencer.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/ha/ManagementIPSystemVMInvestigator.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/ha/RecreatableFencer.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/ha/UserVmDomRInvestigator.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/ha/XenServerInvestigator.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/ha/dao/HighAvailabilityDao.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/ha/dao/HighAvailabilityDaoImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/hypervisor/CloudZonesStartupProcessor.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/hypervisor/HypervisorGuruManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/hypervisor/KVMGuru.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/hypervisor/kvm/discoverer/KvmDummyResourceBase.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/hypervisor/kvm/discoverer/KvmServerDiscoverer.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/metadata/ResourceMetaDataManager.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/metadata/ResourceMetaDataManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/ExternalDeviceUsageManager.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/ExternalDeviceUsageManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/ExternalFirewallDeviceManager.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/ExternalFirewallDeviceManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/ExternalIpAddressAllocator.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/ExternalLoadBalancerDeviceManager.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/ExternalLoadBalancerDeviceManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/ExternalNetworkDeviceManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/IpAddrAllocator.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/Ipv6AddressManager.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/Ipv6AddressManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/NetworkModelImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/NetworkServiceImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/NetworkUsageManager.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/NetworkUsageManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/SshKeysDistriMonitor.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/StorageNetworkManager.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/StorageNetworkManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/as/AutoScaleManager.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/element/CloudZonesNetworkElement.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/element/SecurityGroupElement.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/element/VirtualRouterElement.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/element/VpcVirtualRouterElement.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/firewall/FirewallManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/guru/ControlNetworkGuru.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/guru/DirectNetworkGuru.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/guru/DirectPodBasedNetworkGuru.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/guru/ExternalGuestNetworkGuru.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/guru/GuestNetworkGuru.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/guru/PodBasedNetworkGuru.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/guru/PrivateNetworkGuru.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/guru/PublicNetworkGuru.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/guru/StorageNetworkGuru.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/lb/LBHealthCheckManager.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/lb/LBHealthCheckManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/router/NetworkHelper.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/router/NicProfileHelper.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/router/NicProfileHelperImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/router/RouterControlHelper.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManager.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/router/VpcNetworkHelperImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/router/VpcVirtualNetworkApplianceManager.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/router/VpcVirtualNetworkApplianceManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/rules/AdvancedVpnRules.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/rules/BasicVpnRules.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/rules/DhcpEntryRules.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/rules/DhcpPvlanRules.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/rules/DhcpSubNetRules.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/rules/FirewallRules.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/rules/IpAssociationRules.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/rules/LoadBalancingRules.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/rules/NetworkAclsRules.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/rules/NicPlugInOutRules.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/rules/PasswordToRouterRules.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/rules/PrivateGatewayRules.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/rules/RuleApplier.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/rules/RuleApplierWrapper.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/rules/RulesManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/rules/SshKeyToRouterRules.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/rules/StaticNatImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/rules/StaticNatRules.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/rules/StaticRoutesRules.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/rules/UserdataPwdRules.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/rules/UserdataToRouterRules.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/rules/VirtualNetworkApplianceFactory.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/rules/VpcIpAssociationRules.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/security/LocalSecurityGroupWorkQueue.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/security/RuleUpdateLog.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/security/SecurityGroupListener.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/security/SecurityGroupManager.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/security/SecurityGroupManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/security/SecurityGroupManagerImpl2.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/security/SecurityGroupManagerMBean.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/security/SecurityGroupWorkQueue.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/security/SecurityGroupWorkTracker.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/security/SecurityManagerMBeanImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/vpc/NetworkACLManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/vpc/NetworkACLServiceImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/vpc/PrivateGatewayProfile.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/vpc/PrivateIpAddress.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/vpc/VpcPrivateGatewayTransactionCallable.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/vpn/RemoteAccessVpnManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManager.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/projects/ProjectManager.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/resource/DiscovererBase.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/resource/ResourceChecker.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/server/ConfigurationServer.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/server/ConfigurationServerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/server/Criteria.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/server/LockMasterListener.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/server/ManagementServer.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/server/ManagementServerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/server/StatsCollector.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/server/api/response/BaremetalTemplateResponse.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/server/api/response/NwDeviceDhcpResponse.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/server/api/response/NwDevicePxeServerResponse.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/server/api/response/PxePingResponse.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/server/auth/UserAuthenticator.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/servlet/CloudStartupServlet.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/servlet/ConsoleProxyClientParam.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/servlet/ConsoleProxyPasswordBasedEncryptor.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/servlet/StaticResourceServlet.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/CreateSnapshotPayload.java create mode 100755 cosmic-core/server/src/main/java/com/cloud/storage/ImageStoreUploadMonitor.java create mode 100755 cosmic-core/server/src/main/java/com/cloud/storage/ImageStoreUploadMonitorImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/LocalStoragePoolListener.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/OCFS2Manager.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/OCFS2ManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/RegisterVolumePayload.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/ResizeVolumePayload.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/StorageManager.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/StorageManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/StoragePoolAutomation.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/StoragePoolAutomationImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/TemplateProfile.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/download/DownloadAbandonedState.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/download/DownloadActiveState.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/download/DownloadCompleteState.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/download/DownloadErrorState.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/download/DownloadInProgressState.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/download/DownloadInactiveState.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/download/DownloadListener.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/download/DownloadMonitor.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/download/DownloadMonitorImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/download/DownloadState.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/download/NotDownloadedState.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/listener/SnapshotStateListener.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/listener/StoragePoolMonitor.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/listener/StorageSyncListener.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/listener/VolumeStateListener.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/monitor/StorageHostMonitor.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/resource/DummySecondaryStorageResource.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/secondary/SecStorageVmAlertEventArgs.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/secondary/SecondaryStorageListener.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/secondary/SecondaryStorageVmAllocator.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/secondary/SecondaryStorageVmDefaultAllocator.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/secondary/SecondaryStorageVmManager.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/snapshot/SnapshotManager.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/snapshot/SnapshotScheduler.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/snapshot/SnapshotSchedulerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/upload/NotUploadedState.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/upload/UploadAbandonedState.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/upload/UploadActiveState.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/upload/UploadCompleteState.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/upload/UploadErrorState.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/upload/UploadInProgressState.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/upload/UploadInactiveState.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/upload/UploadListener.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/upload/UploadMonitor.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/upload/UploadMonitorImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/storage/upload/UploadState.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/tags/TaggedResourceManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/template/HypervisorTemplateAdapter.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/template/TemplateAdapter.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/template/TemplateAdapterBase.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/template/TemplateManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/test/DatabaseConfig.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/test/IPRangeConfig.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/test/PodZoneConfig.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/usage/UsageServiceImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/user/AccountManager.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/user/AccountManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/user/DomainManager.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/user/DomainManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/uuididentity/UUIDManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/vm/SystemVmLoadScanHandler.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/vm/SystemVmLoadScanner.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/vm/UserVmManager.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/vm/UserVmStateListener.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/vm/snapshot/VmWorkCreateVMSnapshot.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/vm/snapshot/VmWorkDeleteAllVMSnapshots.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/vm/snapshot/VmWorkDeleteVMSnapshot.java create mode 100644 cosmic-core/server/src/main/java/com/cloud/vm/snapshot/VmWorkRevertToVMSnapshot.java create mode 100644 cosmic-core/server/src/main/java/org/apache/cloudstack/affinity/AffinityGroupServiceImpl.java create mode 100644 cosmic-core/server/src/main/java/org/apache/cloudstack/network/lb/ApplicationLoadBalancerManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/org/apache/cloudstack/network/lb/CertServiceImpl.java create mode 100644 cosmic-core/server/src/main/java/org/apache/cloudstack/network/topology/AdvancedNetworkTopology.java create mode 100644 cosmic-core/server/src/main/java/org/apache/cloudstack/network/topology/AdvancedNetworkVisitor.java create mode 100644 cosmic-core/server/src/main/java/org/apache/cloudstack/network/topology/BasicNetworkTopology.java create mode 100644 cosmic-core/server/src/main/java/org/apache/cloudstack/network/topology/BasicNetworkVisitor.java create mode 100644 cosmic-core/server/src/main/java/org/apache/cloudstack/network/topology/NetworkTopology.java create mode 100644 cosmic-core/server/src/main/java/org/apache/cloudstack/network/topology/NetworkTopologyContext.java create mode 100644 cosmic-core/server/src/main/java/org/apache/cloudstack/network/topology/NetworkTopologyVisitor.java create mode 100644 cosmic-core/server/src/main/java/org/apache/cloudstack/region/RegionAccount.java create mode 100644 cosmic-core/server/src/main/java/org/apache/cloudstack/region/RegionDomain.java create mode 100644 cosmic-core/server/src/main/java/org/apache/cloudstack/region/RegionManager.java create mode 100644 cosmic-core/server/src/main/java/org/apache/cloudstack/region/RegionManagerImpl.java create mode 100644 cosmic-core/server/src/main/java/org/apache/cloudstack/region/RegionServiceImpl.java create mode 100644 cosmic-core/server/src/main/java/org/apache/cloudstack/region/RegionServiceProvider.java create mode 100644 cosmic-core/server/src/main/java/org/apache/cloudstack/region/RegionUser.java create mode 100644 cosmic-core/server/src/main/java/org/apache/cloudstack/region/RegionsApiUtil.java create mode 100644 cosmic-core/server/src/main/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancingRulesServiceImpl.java create mode 100644 cosmic-core/server/src/main/java/org/apache/cloudstack/region/gslb/GslbServiceProvider.java create mode 100644 cosmic-core/server/src/main/java/org/cloud/network/router/deployment/RouterDeploymentDefinition.java create mode 100644 cosmic-core/server/src/main/java/org/cloud/network/router/deployment/RouterDeploymentDefinitionBuilder.java create mode 100644 cosmic-core/server/src/main/java/org/cloud/network/router/deployment/VpcRouterDeploymentDefinition.java create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-misc-context.xml create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/server-alert-adapter-backend/module.properties create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/server-alert-adapter-backend/spring-server-alert-adapter-backend-context.xml create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/server-alert-adapter-compute/module.properties create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/server-alert-adapter-compute/spring-server-alert-adapter-compute-context.xml create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/server-alert-adapter-storage/module.properties create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/server-alert-adapter-storage/spring-server-alert-adapter-storage-context.xml create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/server-allocator/module.properties create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/server-allocator/spring-server-allocator-context.xml create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/server-api/module.properties create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/server-api/spring-server-api-context.xml create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/server-compute/module.properties create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/server-compute/spring-server-compute-context.xml create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/server-discoverer/module.properties create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/server-discoverer/spring-server-discoverer-context.xml create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/server-fencer/module.properties create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/server-fencer/spring-server-fencer-context.xml create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/server-investigator/module.properties create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/server-investigator/spring-server-investigator-context.xml create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/server-network/module.properties create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/server-network/spring-server-network-context.xml create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/server-planner/module.properties create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/server-planner/spring-server-planner-context.xml create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/server-storage/module.properties create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/server-storage/spring-server-storage-context.xml create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/server-template-adapter/module.properties create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/server-template-adapter/spring-server-template-adapter-context.xml create mode 100644 cosmic-core/server/src/main/resources/META-INF/cloudstack/system/spring-server-system-context.xml create mode 100644 cosmic-core/server/src/main/resources/com/cloud/upgrade/databaseCreatorContext.xml create mode 100644 cosmic-core/server/src/test/async-job-component.xml create mode 100644 cosmic-core/server/src/test/java/com/cloud/alert/AlertControlsUnitTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/alert/MockAlertManagerImpl.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/api/APITest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/api/ApiServletTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/api/ListPerfTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/api/LoginResponse.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/api/dispatch/CommandCreationWorkerTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/api/dispatch/DispatchChainFactoryTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/api/dispatch/ParamGenericValidationWorkerTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/api/dispatch/ParamProcessWorkerTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/api/dispatch/SpecificCmdValidationWorkerTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/api/query/dao/SecurityGroupJoinDaoImplTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/capacity/CapacityManagerTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/configuration/ValidateIpRangeTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/consoleproxy/ConsoleProxyManagerTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/event/ActionEventUtilsTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/event/EventControlsUnitTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/ha/HighAvailabilityManagerImplTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/ha/KVMFencerTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/keystore/KeystoreTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/metadata/ResourceMetaDataManagerTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/network/CreatePrivateNetworkTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/network/DedicateGuestVlanRangesTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/network/ExternalLoadBalancerDeviceManagerImplTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/network/MockFirewallManagerImpl.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/network/MockNetworkModelImpl.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/network/NetworkManagerTestComponentLibrary.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/network/NetworkModelTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/network/UpdatePhysicalNetworkTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/network/dao/NetworkDaoTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/network/element/VirtualRouterElementTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/network/element/VpcVirtualRouterElementTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/network/firewall/FirewallManagerTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/network/lb/AssignLoadBalancerTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/network/lb/UpdateLoadBalancerTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/network/router/NetworkHelperImplTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/network/router/RouterControlHelperTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/network/router/VirtualNetworkApplianceManagerImplTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/network/security/SecurityGroupManagerImpl2Test.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/network/security/SecurityGroupManagerImplTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/network/security/SecurityGroupManagerTestConfiguration.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/network/security/SecurityGroupQueueTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/network/vpn/MockRemoteAccessVPNServiceProvider.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/network/vpn/RemoteAccessVpnTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/resource/MockResourceManagerImpl.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/resource/ResourceCheckerTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/resourcelimit/ResourceLimitManagerImplTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/server/ConfigurationServerImplTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/server/ManagementServerImplTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/servlet/ConsoleProxyServletTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/servlet/StaticResourceServletTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/snapshot/SnapshotDaoTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/snapshot/SnapshotDaoTestConfiguration.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/storage/dao/StoragePoolDaoTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/storage/dao/StoragePoolDaoTestConfiguration.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/storage/snapshot/SnapshotManagerTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/template/TemplateManagerImplTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/user/AccountManagerImplTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/user/MockDomainManagerImpl.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vm/FirstFitPlannerTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vm/UserVmManagerTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vm/dao/UserVmCloneSettingDaoImplTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vm/dao/UserVmCloneSettingDaoTestConfiguration.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vm/dao/UserVmDaoImplTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vm/dao/UserVmDaoTestConfiguration.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vm/snapshot/VMSnapshotManagerTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vpc/MockNetworkModelImpl.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vpc/MockResourceLimitManagerImpl.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vpc/MockSite2SiteVpnManagerImpl.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vpc/MockSite2SiteVpnServiceProvider.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vpc/MockVpcVirtualNetworkApplianceManager.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vpc/NetworkACLManagerTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vpc/NetworkACLServiceTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vpc/Site2SiteVpnTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vpc/VpcApiUnitTest.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vpc/VpcTestConfiguration.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vpc/dao/MockConfigurationDaoImpl.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vpc/dao/MockNetworkDaoImpl.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vpc/dao/MockNetworkOfferingDaoImpl.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vpc/dao/MockNetworkOfferingServiceMapDaoImpl.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vpc/dao/MockNetworkServiceMapDaoImpl.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vpc/dao/MockVpcDaoImpl.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vpc/dao/MockVpcOfferingDaoImpl.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vpc/dao/MockVpcOfferingServiceMapDaoImpl.java create mode 100644 cosmic-core/server/src/test/java/com/cloud/vpc/dao/MockVpcVirtualRouterElement.java create mode 100644 cosmic-core/server/src/test/java/org/apache/cloudstack/affinity/AffinityApiUnitTest.java create mode 100644 cosmic-core/server/src/test/java/org/apache/cloudstack/affinity/AffinityGroupServiceImplTest.java create mode 100644 cosmic-core/server/src/test/java/org/apache/cloudstack/network/lb/ApplicationLoadBalancerTest.java create mode 100644 cosmic-core/server/src/test/java/org/apache/cloudstack/network/lb/CertServiceTest.java create mode 100644 cosmic-core/server/src/test/java/org/apache/cloudstack/networkoffering/ChildTestConfiguration.java create mode 100644 cosmic-core/server/src/test/java/org/apache/cloudstack/networkoffering/CreateNetworkOfferingTest.java create mode 100644 cosmic-core/server/src/test/java/org/apache/cloudstack/privategw/AclOnPrivateGwTest.java create mode 100644 cosmic-core/server/src/test/java/org/apache/cloudstack/region/RegionManagerTest.java create mode 100644 cosmic-core/server/src/test/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancingRulesServiceImplTest.java create mode 100644 cosmic-core/server/src/test/java/org/apache/cloudstack/service/ServiceOfferingVOTest.java create mode 100644 cosmic-core/server/src/test/java/org/cloud/network/router/deployment/RouterDeploymentDefinitionTest.java create mode 100644 cosmic-core/server/src/test/java/org/cloud/network/router/deployment/RouterDeploymentDefinitionTestBase.java create mode 100644 cosmic-core/server/src/test/java/org/cloud/network/router/deployment/VpcRouterDeploymentDefinitionTest.java create mode 100644 cosmic-core/server/src/test/resources/CloneSettingDaoTestContext.xml create mode 100644 cosmic-core/server/src/test/resources/SecurityGroupManagerTestContext.xml create mode 100644 cosmic-core/server/src/test/resources/SnapshotDaoTestContext.xml create mode 100644 cosmic-core/server/src/test/resources/StoragePoolDaoTestContext.xml create mode 100644 cosmic-core/server/src/test/resources/UserVMDaoTestContext.xml create mode 100644 cosmic-core/server/src/test/resources/VpcApiUnitTestContext.xml create mode 100644 cosmic-core/server/src/test/resources/VpcTestContext.xml create mode 100644 cosmic-core/server/src/test/resources/appLoadBalancer.xml create mode 100644 cosmic-core/server/src/test/resources/certs/bad_format_cert.crt create mode 100644 cosmic-core/server/src/test/resources/certs/dsa_self_signed.key create mode 100644 cosmic-core/server/src/test/resources/certs/expired_cert.crt create mode 100644 cosmic-core/server/src/test/resources/certs/non_root.crt create mode 100644 cosmic-core/server/src/test/resources/certs/non_root.csr create mode 100644 cosmic-core/server/src/test/resources/certs/non_root.key create mode 100644 cosmic-core/server/src/test/resources/certs/non_x509_pem.crt create mode 100644 cosmic-core/server/src/test/resources/certs/root_chain.crt create mode 100644 cosmic-core/server/src/test/resources/certs/root_chain.csr create mode 100644 cosmic-core/server/src/test/resources/certs/root_chain.key create mode 100644 cosmic-core/server/src/test/resources/certs/rsa_ca_signed.crt create mode 100644 cosmic-core/server/src/test/resources/certs/rsa_ca_signed.csr create mode 100644 cosmic-core/server/src/test/resources/certs/rsa_ca_signed.key create mode 100644 cosmic-core/server/src/test/resources/certs/rsa_self_signed.crt create mode 100644 cosmic-core/server/src/test/resources/certs/rsa_self_signed.csr create mode 100644 cosmic-core/server/src/test/resources/certs/rsa_self_signed.key create mode 100644 cosmic-core/server/src/test/resources/certs/rsa_self_signed_with_pwd.crt create mode 100644 cosmic-core/server/src/test/resources/certs/rsa_self_signed_with_pwd.csr create mode 100644 cosmic-core/server/src/test/resources/certs/rsa_self_signed_with_pwd.key create mode 100644 cosmic-core/server/src/test/resources/cleanup.sql create mode 100644 cosmic-core/server/src/test/resources/createNetworkOffering.xml create mode 100644 cosmic-core/server/src/test/resources/db.properties create mode 100644 cosmic-core/server/src/test/resources/fake.sql create mode 100644 cosmic-core/server/src/test/resources/network-mgr-component.xml create mode 100644 cosmic-core/server/src/test/resources/testContext.xml create mode 100644 cosmic-core/server/src/test/sync-queue-component.xml create mode 100644 cosmic-core/server/test/com/cloud/template/HypervisorTemplateAdapterTest.java create mode 100644 cosmic-core/services/console-proxy/pom.xml create mode 100644 cosmic-core/services/console-proxy/server/pom.xml create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/AjaxFIFOImageCache.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/AuthenticationException.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyAjaxHandler.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyAjaxImageHandler.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyAuthenticationResult.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyBaseServerFactoryImpl.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyClient.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyClientBase.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyClientListener.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyClientParam.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyClientStatsCollector.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyCmdHandler.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyGCThread.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyLoggerFactory.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyMonitor.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyPasswordBasedEncryptor.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyResourceHandler.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxySecureServerFactoryImpl.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyServerFactory.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyThumbnailHandler.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyVncClient.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/InputEventType.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/util/ITileScanListener.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/util/ImageHelper.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/util/Logger.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/util/LoggerFactory.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/util/RawHTTP.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/util/Region.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/util/RegionClassifier.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/util/TileInfo.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/util/TileTracker.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/BufferedImageCanvas.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/FrameBufferCanvas.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/FrameBufferUpdateListener.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/PaintNotificationListener.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/RfbConstants.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/VncClient.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/VncClientPacketSender.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/VncScreenDescription.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/VncServerPacketReceiver.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/packet/client/ClientPacket.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/packet/client/FramebufferUpdateRequestPacket.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/packet/client/KeyboardEventPacket.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/packet/client/MouseEventPacket.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/packet/client/SetEncodingsPacket.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/packet/client/SetPixelFormatPacket.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/packet/server/AbstractRect.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/packet/server/CopyRect.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/packet/server/FrameBufferSizeChangeRequest.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/packet/server/FramebufferUpdatePacket.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/packet/server/RawRect.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/packet/server/Rect.java create mode 100644 cosmic-core/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/vnc/packet/server/ServerCutText.java create mode 100644 cosmic-core/services/pom.xml create mode 100644 cosmic-core/services/secondary-storage/controller/pom.xml create mode 100644 cosmic-core/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/PremiumSecondaryStorageManagerImpl.java create mode 100644 cosmic-core/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java create mode 100644 cosmic-core/services/secondary-storage/controller/src/main/resources/META-INF/cloudstack/core/spring-services-secondary-storage-controller-core-context.xml create mode 100644 cosmic-core/services/secondary-storage/controller/test/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerTest.java create mode 100644 cosmic-core/services/secondary-storage/pom.xml create mode 100644 cosmic-core/services/secondary-storage/server/pom.xml create mode 100644 cosmic-core/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/HttpUploadServerHandler.java create mode 100644 cosmic-core/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/LocalNfsSecondaryStorageResource.java create mode 100644 cosmic-core/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/LocalSecondaryStorageResource.java create mode 100644 cosmic-core/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java create mode 100644 cosmic-core/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/SecondaryStorageDiscoverer.java create mode 100644 cosmic-core/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/SecondaryStorageResource.java create mode 100644 cosmic-core/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/SecondaryStorageResourceHandler.java create mode 100644 cosmic-core/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/template/DownloadManager.java create mode 100644 cosmic-core/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/template/DownloadManagerImpl.java create mode 100644 cosmic-core/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/template/UploadEntity.java create mode 100644 cosmic-core/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/template/UploadManager.java create mode 100644 cosmic-core/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/template/UploadManagerImpl.java create mode 100644 cosmic-core/services/secondary-storage/server/src/main/resources/META-INF/cloudstack/secondary-storage-discoverer/module.properties create mode 100644 cosmic-core/services/secondary-storage/server/src/main/resources/META-INF/cloudstack/secondary-storage-discoverer/spring-secondary-storage-discoverer-context.xml create mode 100644 cosmic-core/services/secondary-storage/server/src/test/java/org/apache/cloudstack/storage/resource/LocalNfsSecondaryStorageResourceTest.java create mode 100644 cosmic-core/services/secondary-storage/server/src/test/java/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResourceTest.java create mode 100644 cosmic-core/setup/bindir/cloud-migrate-databases.in create mode 100755 cosmic-core/setup/bindir/cloud-set-guest-password.in create mode 100755 cosmic-core/setup/bindir/cloud-set-guest-sshkey.in create mode 100755 cosmic-core/setup/bindir/cloud-setup-databases.in create mode 100755 cosmic-core/setup/bindir/cloud-setup-encryption.in create mode 100755 cosmic-core/setup/bindir/cloud-sysvmadm.in create mode 100644 cosmic-core/setup/db/221to222upgrade.sh create mode 100644 cosmic-core/setup/db/22beta4to22GA.sql create mode 100644 cosmic-core/setup/db/create-database-premium.sql create mode 100644 cosmic-core/setup/db/create-database.sql create mode 100644 cosmic-core/setup/db/create-schema-premium.sql create mode 100755 cosmic-core/setup/db/create-schema.sql create mode 100644 cosmic-core/setup/db/data-20to21.sql create mode 100644 cosmic-core/setup/db/data-22beta1to22beta2.sql create mode 100755 cosmic-core/setup/db/db/data-217to218.sql create mode 100644 cosmic-core/setup/db/db/schema-20to21.sql create mode 100644 cosmic-core/setup/db/db/schema-217to218.sql create mode 100644 cosmic-core/setup/db/db/schema-21to22-cleanup.sql create mode 100755 cosmic-core/setup/db/db/schema-21to22-premium.sql create mode 100755 cosmic-core/setup/db/db/schema-21to22.sql create mode 100644 cosmic-core/setup/db/db/schema-2210to2211.sql create mode 100644 cosmic-core/setup/db/db/schema-2211to2212-premium.sql create mode 100644 cosmic-core/setup/db/db/schema-2211to2212.sql create mode 100644 cosmic-core/setup/db/db/schema-2212to2213.sql create mode 100644 cosmic-core/setup/db/db/schema-2213to2214.sql create mode 100644 cosmic-core/setup/db/db/schema-2214to30-cleanup.sql create mode 100755 cosmic-core/setup/db/db/schema-2214to30.sql create mode 100644 cosmic-core/setup/db/db/schema-221to222-cleanup.sql create mode 100755 cosmic-core/setup/db/db/schema-221to222-premium.sql create mode 100644 cosmic-core/setup/db/db/schema-221to222.sql create mode 100644 cosmic-core/setup/db/db/schema-222to224-cleanup.sql create mode 100755 cosmic-core/setup/db/db/schema-222to224-premium.sql create mode 100644 cosmic-core/setup/db/db/schema-222to224.sql create mode 100644 cosmic-core/setup/db/db/schema-224to225-cleanup.sql create mode 100644 cosmic-core/setup/db/db/schema-224to225.sql create mode 100644 cosmic-core/setup/db/db/schema-225to226.sql create mode 100755 cosmic-core/setup/db/db/schema-227to228-premium.sql create mode 100644 cosmic-core/setup/db/db/schema-227to228.sql create mode 100644 cosmic-core/setup/db/db/schema-228to229.sql create mode 100644 cosmic-core/setup/db/db/schema-229to2210.sql create mode 100644 cosmic-core/setup/db/db/schema-22beta1to22beta2.sql create mode 100755 cosmic-core/setup/db/db/schema-22beta3to22beta4.sql create mode 100644 cosmic-core/setup/db/db/schema-301to302-cleanup.sql create mode 100755 cosmic-core/setup/db/db/schema-301to302.sql create mode 100755 cosmic-core/setup/db/db/schema-302to303.sql create mode 100644 cosmic-core/setup/db/db/schema-302to40-cleanup.sql create mode 100644 cosmic-core/setup/db/db/schema-302to40.sql create mode 100644 cosmic-core/setup/db/db/schema-304to305-cleanup.sql create mode 100755 cosmic-core/setup/db/db/schema-304to305.sql create mode 100644 cosmic-core/setup/db/db/schema-305to306-cleanup.sql create mode 100755 cosmic-core/setup/db/db/schema-305to306.sql create mode 100644 cosmic-core/setup/db/db/schema-306to307.sql create mode 100644 cosmic-core/setup/db/db/schema-307to410-cleanup.sql create mode 100644 cosmic-core/setup/db/db/schema-307to410.sql create mode 100755 cosmic-core/setup/db/db/schema-30to301.sql create mode 100644 cosmic-core/setup/db/db/schema-40to410-cleanup.sql create mode 100644 cosmic-core/setup/db/db/schema-40to410.sql create mode 100644 cosmic-core/setup/db/db/schema-410to420-cleanup.sql create mode 100644 cosmic-core/setup/db/db/schema-410to420.sql create mode 100644 cosmic-core/setup/db/db/schema-420to421.sql create mode 100644 cosmic-core/setup/db/db/schema-421to430-cleanup.sql create mode 100644 cosmic-core/setup/db/db/schema-421to430.sql create mode 100644 cosmic-core/setup/db/db/schema-430to440-cleanup.sql create mode 100644 cosmic-core/setup/db/db/schema-430to440.sql create mode 100644 cosmic-core/setup/db/db/schema-440to441-cleanup.sql create mode 100644 cosmic-core/setup/db/db/schema-440to441.sql create mode 100644 cosmic-core/setup/db/db/schema-441to442.sql create mode 100644 cosmic-core/setup/db/db/schema-442to450-cleanup.sql create mode 100644 cosmic-core/setup/db/db/schema-442to450.sql create mode 100644 cosmic-core/setup/db/db/schema-443to444.sql create mode 100644 cosmic-core/setup/db/db/schema-450to451-cleanup.sql create mode 100644 cosmic-core/setup/db/db/schema-450to451.sql create mode 100644 cosmic-core/setup/db/db/schema-451to452-cleanup.sql create mode 100644 cosmic-core/setup/db/db/schema-451to452.sql create mode 100644 cosmic-core/setup/db/db/schema-452to453-cleanup.sql create mode 100644 cosmic-core/setup/db/db/schema-452to453.sql create mode 100644 cosmic-core/setup/db/db/schema-452to460-cleanup.sql create mode 100644 cosmic-core/setup/db/db/schema-452to460.sql create mode 100644 cosmic-core/setup/db/db/schema-460to461-cleanup.sql create mode 100644 cosmic-core/setup/db/db/schema-460to461.sql create mode 100644 cosmic-core/setup/db/db/schema-461to470-cleanup.sql create mode 100644 cosmic-core/setup/db/db/schema-461to470.sql create mode 100644 cosmic-core/setup/db/db/schema-470to471-cleanup.sql create mode 100644 cosmic-core/setup/db/db/schema-470to471.sql create mode 100644 cosmic-core/setup/db/db/schema-471to480-cleanup.sql create mode 100644 cosmic-core/setup/db/db/schema-471to480.sql create mode 100644 cosmic-core/setup/db/db/schema-480to500-cleanup.sql create mode 100644 cosmic-core/setup/db/db/schema-480to500.sql create mode 100644 cosmic-core/setup/db/db/schema-500to501-cleanup.sql create mode 100644 cosmic-core/setup/db/db/schema-500to501.sql create mode 100644 cosmic-core/setup/db/db/schema-501to510-cleanup.sql create mode 100644 cosmic-core/setup/db/db/schema-501to510.sql create mode 100644 cosmic-core/setup/db/db/schema-level.sql create mode 100644 cosmic-core/setup/db/db/schema-snapshot-217to224.sql create mode 100644 cosmic-core/setup/db/db/schema-snapshot-223to224.sql create mode 100644 cosmic-core/setup/db/deploy-db-clouddev.sh create mode 100755 cosmic-core/setup/db/deploy-db-dev.sh create mode 100644 cosmic-core/setup/db/index-20to21.sql create mode 100644 cosmic-core/setup/db/index-212to213.sql create mode 100644 cosmic-core/setup/db/postprocess-20to21.sql create mode 100644 cosmic-core/setup/db/server-setup.sql create mode 100755 cosmic-core/setup/db/server-setup.xml create mode 100755 cosmic-core/setup/db/templates.sql create mode 100755 cosmic-core/systemvm/bindir/cloud-setup-console-proxy.in create mode 100644 cosmic-core/systemvm/certs/localhost.crt create mode 100644 cosmic-core/systemvm/certs/localhost.key create mode 100644 cosmic-core/systemvm/certs/realhostip.crt create mode 100644 cosmic-core/systemvm/certs/realhostip.csr create mode 100644 cosmic-core/systemvm/certs/realhostip.key create mode 100644 cosmic-core/systemvm/certs/realhostip.keystore create mode 100644 cosmic-core/systemvm/conf.dom0/agent.properties.in create mode 100644 cosmic-core/systemvm/conf.dom0/consoleproxy.properties.in create mode 100644 cosmic-core/systemvm/conf.dom0/log4j-cloud.xml.in create mode 100644 cosmic-core/systemvm/conf/agent.properties create mode 100644 cosmic-core/systemvm/conf/agent.properties.ssvm create mode 100644 cosmic-core/systemvm/conf/consoleproxy.properties create mode 100644 cosmic-core/systemvm/conf/environment.properties create mode 100644 cosmic-core/systemvm/conf/log4j-cloud.xml create mode 100644 cosmic-core/systemvm/css/ajaxviewer.css create mode 100644 cosmic-core/systemvm/css/logger.css create mode 100644 cosmic-core/systemvm/distro/centos/SYSCONFDIR/rc.d/init.d/cloud-console-proxy.in create mode 100644 cosmic-core/systemvm/distro/fedora/SYSCONFDIR/rc.d/init.d/cloud-console-proxy.in create mode 100644 cosmic-core/systemvm/distro/rhel/SYSCONFDIR/rc.d/init.d/cloud-console-proxy.in create mode 100755 cosmic-core/systemvm/distro/ubuntu/SYSCONFDIR/init.d/cloud-console-proxy.in create mode 100644 cosmic-core/systemvm/images/back.gif create mode 100644 cosmic-core/systemvm/images/bright-green.png create mode 100644 cosmic-core/systemvm/images/cad.gif create mode 100644 cosmic-core/systemvm/images/cannotconnect.jpg create mode 100644 cosmic-core/systemvm/images/clr_button.gif create mode 100644 cosmic-core/systemvm/images/clr_button_hover.gif create mode 100644 cosmic-core/systemvm/images/dot.cur create mode 100644 cosmic-core/systemvm/images/gray-green.png create mode 100644 cosmic-core/systemvm/images/grid_headerbg.gif create mode 100644 cosmic-core/systemvm/images/left.png create mode 100644 cosmic-core/systemvm/images/minimize_button.gif create mode 100644 cosmic-core/systemvm/images/minimize_button_hover.gif create mode 100644 cosmic-core/systemvm/images/notready.jpg create mode 100644 cosmic-core/systemvm/images/play_button.gif create mode 100644 cosmic-core/systemvm/images/play_button_hover.gif create mode 100644 cosmic-core/systemvm/images/right.png create mode 100644 cosmic-core/systemvm/images/right2.png create mode 100644 cosmic-core/systemvm/images/shrink_button.gif create mode 100644 cosmic-core/systemvm/images/shrink_button_hover.gif create mode 100644 cosmic-core/systemvm/images/stop_button.gif create mode 100644 cosmic-core/systemvm/images/stop_button_hover.gif create mode 100644 cosmic-core/systemvm/images/winlog.png create mode 100644 cosmic-core/systemvm/js/ajaxkeys.js create mode 100644 cosmic-core/systemvm/js/ajaxviewer.js create mode 100644 cosmic-core/systemvm/js/cloud.logger.js create mode 100644 cosmic-core/systemvm/js/handler.js create mode 100644 cosmic-core/systemvm/js/jquery.js create mode 100755 cosmic-core/systemvm/libexec/console-proxy-runner.in create mode 100644 cosmic-core/systemvm/patches/debian/README create mode 100755 cosmic-core/systemvm/patches/debian/buildsystemvm.sh create mode 100644 cosmic-core/systemvm/patches/debian/config.dat create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/apache2/httpd.conf create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/apache2/ports.conf create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/apache2/sites-available/default create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/apache2/sites-available/default-ssl create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/apache2/vhostexample.conf create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/chef/node.json create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/chef/solo.rb create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/cloud-nic.rules create mode 100755 cosmic-core/systemvm/patches/debian/config/etc/cron.daily/cloud-cleanup create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/default/cloud create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/default/cloud-passwd-srvr create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/dnsmasq.conf.tmpl create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/haproxy/haproxy.cfg create mode 100755 cosmic-core/systemvm/patches/debian/config/etc/init.d/cloud create mode 100755 cosmic-core/systemvm/patches/debian/config/etc/init.d/cloud-early-config create mode 100755 cosmic-core/systemvm/patches/debian/config/etc/init.d/cloud-passwd-srvr create mode 100755 cosmic-core/systemvm/patches/debian/config/etc/init.d/postinit create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/iptables/iptables-consoleproxy create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/iptables/iptables-dhcpsrvr create mode 100755 cosmic-core/systemvm/patches/debian/config/etc/iptables/iptables-elbvm create mode 100755 cosmic-core/systemvm/patches/debian/config/etc/iptables/iptables-ilbvm create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/iptables/iptables-router create mode 100755 cosmic-core/systemvm/patches/debian/config/etc/iptables/iptables-secstorage create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/iptables/iptables-vpcrouter create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/iptables/rt_tables_init create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/iptables/rules create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/logrotate.conf create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/logrotate.d/apache2 create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/logrotate.d/cloud create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/logrotate.d/conntrackd create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/logrotate.d/dnsmasq create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/logrotate.d/haproxy create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/logrotate.d/ppp create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/logrotate.d/rsyslog create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/modprobe.d/aesni_intel.conf create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/modprobe.d/pcspkr.conf create mode 100755 cosmic-core/systemvm/patches/debian/config/etc/profile.d/cloud.sh create mode 100755 cosmic-core/systemvm/patches/debian/config/etc/rc.local create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/rsyslog.conf create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/ssh/sshd_config create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/sysctl.conf create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/udev/rules.d/75-persistent-net-generator.rules create mode 100644 cosmic-core/systemvm/patches/debian/config/etc/vpcdnsmasq.conf create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/baremetal_snat.sh create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/bumpup_priority.sh create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/checkbatchs2svpn.sh create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/checkrouter.sh create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/checks2svpn.sh create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cloud-nic.sh create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/configure.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs/CsAddress.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs/CsApp.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs/CsConfig.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs/CsDatabag.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs/CsDhcp.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs/CsFile.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs/CsGuestNetwork.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs/CsHelper.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs/CsLoadBalancer.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs/CsMonitor.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs/CsNetfilter.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs/CsProcess.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs/CsRedundant.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs/CsRoute.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs/CsRule.py create mode 100644 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs/CsStaticRoutes.py create mode 100644 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs/CsVmPassword.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs/__init__.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs_cmdline.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs_dhcp.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs_firewallrules.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs_forwardingrules.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs_guestnetwork.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs_ip.py create mode 100644 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs_iptables_save.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs_loadbalancer.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs_monitorservice.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs_network_acl.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs_remoteaccessvpn.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs_site2sitevpn.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs_staticroutes.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs_vmdata.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/cs_vpnusers.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/dnsmasq.sh create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/edithosts.sh create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/get_template_version.sh create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/ilb.sh create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/ipassoc.sh create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/ipsectunnel.sh create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/line_edit.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/loadbalancer.sh create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/master.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/merge.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/monitor_service.sh create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/netusage.sh create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/passwd_server create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/passwd_server_ip create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/passwd_server_ip.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/patchsystemvm.sh create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/savepassword.sh create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/set_redundant.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/test.sh create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/update_config.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/vmdata.py create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/vpc_func.sh create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/vpc_netusage.sh create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/vpc_passwd_server create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/vpc_snat.sh create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/vpc_staticroute.sh create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/bin/vr_cfg.sh create mode 100644 cosmic-core/systemvm/patches/debian/config/opt/cloud/templates/README create mode 100644 cosmic-core/systemvm/patches/debian/config/opt/cloud/templates/arping_gateways.sh.templ create mode 100644 cosmic-core/systemvm/patches/debian/config/opt/cloud/templates/check_bumpup.sh create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/templates/check_heartbeat.sh.templ create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/templates/checkrouter.sh.templ create mode 100644 cosmic-core/systemvm/patches/debian/config/opt/cloud/templates/conntrackd.conf.templ create mode 100755 cosmic-core/systemvm/patches/debian/config/opt/cloud/templates/heartbeat.sh.templ create mode 100644 cosmic-core/systemvm/patches/debian/config/opt/cloud/templates/keepalived.conf.templ create mode 100644 cosmic-core/systemvm/patches/debian/config/opt/cloud/testdata/README create mode 100644 cosmic-core/systemvm/patches/debian/config/opt/cloud/testdata/acl0001.json create mode 100644 cosmic-core/systemvm/patches/debian/config/opt/cloud/testdata/dhcp0001.json create mode 100644 cosmic-core/systemvm/patches/debian/config/opt/cloud/testdata/gn0001.json create mode 100644 cosmic-core/systemvm/patches/debian/config/opt/cloud/testdata/ips0001.json create mode 100644 cosmic-core/systemvm/patches/debian/config/opt/cloud/testdata/ips0002.json create mode 100644 cosmic-core/systemvm/patches/debian/config/opt/cloud/testdata/ips0003.json create mode 100644 cosmic-core/systemvm/patches/debian/config/opt/cloud/testdata/s2s0001.json create mode 100644 cosmic-core/systemvm/patches/debian/config/opt/cloud/testdata/vmp0001.json create mode 100644 cosmic-core/systemvm/patches/debian/config/root/.ssh/authorized_keys create mode 100755 cosmic-core/systemvm/patches/debian/config/root/clearUsageRules.sh create mode 100644 cosmic-core/systemvm/patches/debian/config/root/func.sh create mode 100755 cosmic-core/systemvm/patches/debian/config/root/monitorServices.py create mode 100755 cosmic-core/systemvm/patches/debian/config/root/reconfigLB.sh create mode 100644 cosmic-core/systemvm/patches/debian/config/root/redundant_router/arping_gateways.sh.templ create mode 100644 cosmic-core/systemvm/patches/debian/config/root/redundant_router/backup.sh.templ create mode 100644 cosmic-core/systemvm/patches/debian/config/root/redundant_router/check_bumpup.sh create mode 100755 cosmic-core/systemvm/patches/debian/config/root/redundant_router/check_heartbeat.sh.templ create mode 100755 cosmic-core/systemvm/patches/debian/config/root/redundant_router/checkrouter.sh.templ create mode 100644 cosmic-core/systemvm/patches/debian/config/root/redundant_router/conntrackd.conf.templ create mode 100644 cosmic-core/systemvm/patches/debian/config/root/redundant_router/disable_pubip.sh create mode 100644 cosmic-core/systemvm/patches/debian/config/root/redundant_router/enable_pubip.sh.templ create mode 100644 cosmic-core/systemvm/patches/debian/config/root/redundant_router/fault.sh.templ create mode 100755 cosmic-core/systemvm/patches/debian/config/root/redundant_router/heartbeat.sh.templ create mode 100644 cosmic-core/systemvm/patches/debian/config/root/redundant_router/keepalived.conf.templ create mode 100644 cosmic-core/systemvm/patches/debian/config/root/redundant_router/master.sh.templ create mode 100644 cosmic-core/systemvm/patches/debian/config/root/redundant_router/primary-backup.sh.templ create mode 100644 cosmic-core/systemvm/patches/debian/config/root/redundant_router/services.sh create mode 100644 cosmic-core/systemvm/patches/debian/config/var/www/html/latest/.htaccess create mode 100644 cosmic-core/systemvm/patches/debian/config/var/www/html/userdata/.htaccess create mode 100755 cosmic-core/systemvm/patches/debian/convert.sh create mode 100755 cosmic-core/systemvm/patches/debian/qemuconvert.sh create mode 100644 cosmic-core/systemvm/patches/debian/systemvm.vmx create mode 100644 cosmic-core/systemvm/patches/debian/systemvm.xml create mode 100755 cosmic-core/systemvm/patches/debian/vhdconvert.sh create mode 100644 cosmic-core/systemvm/patches/debian/vpn/etc/ipsec.conf create mode 100644 cosmic-core/systemvm/patches/debian/vpn/etc/ipsec.d/l2tp.conf create mode 100644 cosmic-core/systemvm/patches/debian/vpn/etc/ipsec.secrets create mode 100644 cosmic-core/systemvm/patches/debian/vpn/etc/ppp/options.xl2tpd create mode 100644 cosmic-core/systemvm/patches/debian/vpn/etc/xl2tpd/xl2tpd.conf create mode 100755 cosmic-core/systemvm/patches/debian/vpn/opt/cloud/bin/vpn_l2tp.sh create mode 100644 cosmic-core/systemvm/patches/debian/xe/xe-daemon create mode 100644 cosmic-core/systemvm/patches/debian/xe/xe-linux-distribution create mode 100644 cosmic-core/systemvm/patches/debian/xe/xe-update-guest-attrs create mode 100644 cosmic-core/systemvm/pom.xml create mode 100755 cosmic-core/systemvm/scripts/_run.sh create mode 100755 cosmic-core/systemvm/scripts/config_auth.sh create mode 100755 cosmic-core/systemvm/scripts/config_ssl.sh create mode 100755 cosmic-core/systemvm/scripts/consoleproxy.sh create mode 100755 cosmic-core/systemvm/scripts/ipfirewall.sh create mode 100644 cosmic-core/systemvm/scripts/run-proxy.sh create mode 100644 cosmic-core/systemvm/scripts/run.bat create mode 100755 cosmic-core/systemvm/scripts/run.sh create mode 100755 cosmic-core/systemvm/scripts/secstorage.sh create mode 100644 cosmic-core/systemvm/scripts/ssvm-check.sh create mode 100644 cosmic-core/systemvm/scripts/utils.sh create mode 100644 cosmic-core/systemvm/systemvm-descriptor.xml create mode 100644 cosmic-core/systemvm/test/python/TestCsAddress.py create mode 100644 cosmic-core/systemvm/test/python/TestCsApp.py create mode 100644 cosmic-core/systemvm/test/python/TestCsCmdLine.py create mode 100644 cosmic-core/systemvm/test/python/TestCsConfig.py create mode 100644 cosmic-core/systemvm/test/python/TestCsDatabag.py create mode 100644 cosmic-core/systemvm/test/python/TestCsDhcp.py create mode 100644 cosmic-core/systemvm/test/python/TestCsFile.py create mode 100644 cosmic-core/systemvm/test/python/TestCsGuestNetwork.py create mode 100644 cosmic-core/systemvm/test/python/TestCsHelper.py create mode 100644 cosmic-core/systemvm/test/python/TestCsInterface.py create mode 100644 cosmic-core/systemvm/test/python/TestCsNetfilter.py create mode 100644 cosmic-core/systemvm/test/python/TestCsProcess.py create mode 100644 cosmic-core/systemvm/test/python/TestCsRedundant.py create mode 100644 cosmic-core/systemvm/test/python/TestCsRoute.py create mode 100644 cosmic-core/systemvm/test/python/TestCsRule.py create mode 100644 cosmic-core/systemvm/test/python/runtests.sh create mode 100644 cosmic-core/systemvm/ui/viewer-bad-sid.ftl create mode 100644 cosmic-core/systemvm/ui/viewer-connect-failed.ftl create mode 100644 cosmic-core/systemvm/ui/viewer-update.ftl create mode 100644 cosmic-core/systemvm/ui/viewer.ftl create mode 100644 cosmic-core/systemvm/vm-script/vmops create mode 100755 cosmic-core/test/bindirbak/cloud-run-test.in create mode 100644 cosmic-core/test/conf/config.xml create mode 100644 cosmic-core/test/conf/deploy.properties create mode 100644 cosmic-core/test/conf/deploy.xml create mode 100644 cosmic-core/test/conf/log4j-stdout.properties create mode 100644 cosmic-core/test/conf/log4j.properties create mode 100644 cosmic-core/test/conf/templates.sql create mode 100644 cosmic-core/test/conf/tool.properties create mode 100644 cosmic-core/test/integration/__init__.py create mode 100644 cosmic-core/test/integration/component/cpu_limits/__init__.py create mode 100644 cosmic-core/test/integration/component/maint/__init__.py create mode 100644 cosmic-core/test/integration/component/maint/test_bugs.py create mode 100644 cosmic-core/test/integration/component/maint/test_dedicate_guest_vlan_ranges.py create mode 100644 cosmic-core/test/integration/component/maint/test_dedicate_public_ip_range.py create mode 100644 cosmic-core/test/integration/component/maint/test_egress_rules_host_maintenance.py create mode 100644 cosmic-core/test/integration/component/maint/test_escalation_templates.py create mode 100644 cosmic-core/test/integration/component/maint/test_escalations_hosts.py create mode 100644 cosmic-core/test/integration/component/maint/test_explicit_dedication.py create mode 100644 cosmic-core/test/integration/component/maint/test_high_availability.py create mode 100644 cosmic-core/test/integration/component/maint/test_host_high_availability.py create mode 100644 cosmic-core/test/integration/component/maint/test_hypervisor_limit.py create mode 100644 cosmic-core/test/integration/component/maint/test_multiple_ip_ranges.py create mode 100644 cosmic-core/test/integration/component/maint/test_redundant_router.py create mode 100644 cosmic-core/test/integration/component/maint/test_redundant_router_deployment_planning.py create mode 100644 cosmic-core/test/integration/component/maint/test_redundant_router_network_rules.py create mode 100644 cosmic-core/test/integration/component/maint/test_vpc_host_maintenance.py create mode 100644 cosmic-core/test/integration/component/maint/test_vpc_on_host_maintenance.py create mode 100644 cosmic-core/test/integration/component/maint/test_zone_level_local_storage_setting.py create mode 100644 cosmic-core/test/integration/component/maint/testpath_disable_enable_zone.py create mode 100644 cosmic-core/test/integration/component/maint/testpath_disablestoragepool.py create mode 100644 cosmic-core/test/integration/component/maint/testpath_vMotion_vmware.py create mode 100644 cosmic-core/test/integration/component/test_VirtualRouter_alerts.py create mode 100644 cosmic-core/test/integration/component/test_accounts.py create mode 100644 cosmic-core/test/integration/component/test_add_remove_network.py create mode 100644 cosmic-core/test/integration/component/test_blocker_bugs.py create mode 100644 cosmic-core/test/integration/component/test_browse_templates.py create mode 100644 cosmic-core/test/integration/component/test_browse_volumes.py create mode 100644 cosmic-core/test/integration/component/test_concurrent_snapshots_limit.py create mode 100644 cosmic-core/test/integration/component/test_cpu_domain_limits.py create mode 100644 cosmic-core/test/integration/component/test_cpu_limits.py create mode 100644 cosmic-core/test/integration/component/test_cpu_project_limits.py create mode 100644 cosmic-core/test/integration/component/test_deploy_vgpu_vm.py create mode 100755 cosmic-core/test/integration/component/test_deploy_vm_userdata_reg.py create mode 100644 cosmic-core/test/integration/component/test_dynamic_compute_offering.py create mode 100755 cosmic-core/test/integration/component/test_egress_fw_rules.py create mode 100644 cosmic-core/test/integration/component/test_escalation_listTemplateDomainAdmin.py create mode 100644 cosmic-core/test/integration/component/test_escalations_instances.py create mode 100644 cosmic-core/test/integration/component/test_escalations_ipaddresses.py create mode 100644 cosmic-core/test/integration/component/test_escalations_isos.py create mode 100644 cosmic-core/test/integration/component/test_escalations_networks.py create mode 100644 cosmic-core/test/integration/component/test_escalations_routers.py create mode 100644 cosmic-core/test/integration/component/test_escalations_securitygroups.py create mode 100644 cosmic-core/test/integration/component/test_escalations_snapshots.py create mode 100644 cosmic-core/test/integration/component/test_escalations_templates.py create mode 100644 cosmic-core/test/integration/component/test_escalations_vmware.py create mode 100644 cosmic-core/test/integration/component/test_escalations_volumes.py create mode 100644 cosmic-core/test/integration/component/test_escalations_vpncustomergateways.py create mode 100644 cosmic-core/test/integration/component/test_haproxy.py create mode 100644 cosmic-core/test/integration/component/test_host_maintenance.py create mode 100644 cosmic-core/test/integration/component/test_interop_xd_ccp.py create mode 100644 cosmic-core/test/integration/component/test_ip_reservation.py create mode 100644 cosmic-core/test/integration/component/test_lb_secondary_ip.py create mode 100644 cosmic-core/test/integration/component/test_memory_limits.py create mode 100644 cosmic-core/test/integration/component/test_mm_domain_limits.py create mode 100644 cosmic-core/test/integration/component/test_mm_project_limits.py create mode 100644 cosmic-core/test/integration/component/test_overcommit.py create mode 100644 cosmic-core/test/integration/component/test_persistent_networks.py create mode 100644 cosmic-core/test/integration/component/test_portable_ip.py create mode 100644 cosmic-core/test/integration/component/test_project_limits.py create mode 100644 cosmic-core/test/integration/component/test_project_usage.py create mode 100644 cosmic-core/test/integration/component/test_ps_domain_limits.py create mode 100644 cosmic-core/test/integration/component/test_ps_limits.py create mode 100644 cosmic-core/test/integration/component/test_ps_resize_volume.py create mode 100644 cosmic-core/test/integration/component/test_ps_resource_limits_volume.py create mode 100644 cosmic-core/test/integration/component/test_recurring_snapshots.py create mode 100644 cosmic-core/test/integration/component/test_reset_ssh_keypair.py create mode 100644 cosmic-core/test/integration/component/test_routers.py create mode 100755 cosmic-core/test/integration/component/test_security_groups.py create mode 100644 cosmic-core/test/integration/component/test_shared_networks.py create mode 100644 cosmic-core/test/integration/component/test_simultaneous_volume_attach.py create mode 100644 cosmic-core/test/integration/component/test_snapshot_gc.py create mode 100644 cosmic-core/test/integration/component/test_snapshot_limits.py create mode 100644 cosmic-core/test/integration/component/test_snapshots.py create mode 100644 cosmic-core/test/integration/component/test_snapshots_improvement.py create mode 100644 cosmic-core/test/integration/component/test_ss_domain_limits.py create mode 100644 cosmic-core/test/integration/component/test_ss_limits.py create mode 100644 cosmic-core/test/integration/component/test_ss_max_limits.py create mode 100644 cosmic-core/test/integration/component/test_ss_project_limits.py create mode 100644 cosmic-core/test/integration/component/test_stopped_vm.py create mode 100644 cosmic-core/test/integration/component/test_storage_motion.py create mode 100644 cosmic-core/test/integration/component/test_templates.py create mode 100644 cosmic-core/test/integration/component/test_usage.py create mode 100644 cosmic-core/test/integration/component/test_vm_passwdenabled.py create mode 100644 cosmic-core/test/integration/component/test_vmware_drs.py create mode 100644 cosmic-core/test/integration/component/test_volumes.py create mode 100644 cosmic-core/test/integration/component/test_vpc.py create mode 100644 cosmic-core/test/integration/component/test_vpc_network.py create mode 100644 cosmic-core/test/integration/component/test_vpc_network_lbrules.py create mode 100644 cosmic-core/test/integration/component/test_vpc_network_pfrules.py create mode 100644 cosmic-core/test/integration/component/test_vpc_network_staticnatrule.py create mode 100644 cosmic-core/test/integration/component/test_vpc_offerings.py create mode 100644 cosmic-core/test/integration/component/test_vpc_routers.py create mode 100644 cosmic-core/test/integration/component/test_vpc_vm_life_cycle.py create mode 100644 cosmic-core/test/integration/component/test_vpc_vms_deployment.py create mode 100644 cosmic-core/test/integration/plugins/test_nicira.py create mode 100644 cosmic-core/test/integration/smoke/__init__.py create mode 100644 cosmic-core/test/integration/smoke/misc/__init__.py create mode 100644 cosmic-core/test/integration/smoke/misc/test_escalations_templates.py create mode 100644 cosmic-core/test/integration/smoke/test_affinity_groups.py create mode 100644 cosmic-core/test/integration/smoke/test_affinity_groups_projects.py create mode 100644 cosmic-core/test/integration/smoke/test_deploy_vgpu_enabled_vm.py create mode 100644 cosmic-core/test/integration/smoke/test_deploy_vm_iso.py create mode 100644 cosmic-core/test/integration/smoke/test_deploy_vm_root_resize.py create mode 100644 cosmic-core/test/integration/smoke/test_deploy_vm_with_userdata.py create mode 100644 cosmic-core/test/integration/smoke/test_deploy_vms_with_varied_deploymentplanners.py create mode 100644 cosmic-core/test/integration/smoke/test_disk_offerings.py create mode 100644 cosmic-core/test/integration/smoke/test_global_settings.py create mode 100644 cosmic-core/test/integration/smoke/test_guest_vlan_range.py create mode 100644 cosmic-core/test/integration/smoke/test_hosts.py create mode 100644 cosmic-core/test/integration/smoke/test_internal_lb.py create mode 100755 cosmic-core/test/integration/smoke/test_iso.py create mode 100644 cosmic-core/test/integration/smoke/test_loadbalance.py create mode 100644 cosmic-core/test/integration/smoke/test_multipleips_per_nic.py create mode 100644 cosmic-core/test/integration/smoke/test_network.py create mode 100644 cosmic-core/test/integration/smoke/test_network_acl.py create mode 100644 cosmic-core/test/integration/smoke/test_nic.py create mode 100644 cosmic-core/test/integration/smoke/test_nic_adapter_type.py create mode 100644 cosmic-core/test/integration/smoke/test_non_contigiousvlan.py create mode 100644 cosmic-core/test/integration/smoke/test_over_provisioning.py create mode 100644 cosmic-core/test/integration/smoke/test_password_server.py create mode 100644 cosmic-core/test/integration/smoke/test_portable_publicip.py create mode 100644 cosmic-core/test/integration/smoke/test_portforward_remove.py create mode 100644 cosmic-core/test/integration/smoke/test_primary_storage.py create mode 100644 cosmic-core/test/integration/smoke/test_privategw_acl.py create mode 100644 cosmic-core/test/integration/smoke/test_public_ip_range.py create mode 100644 cosmic-core/test/integration/smoke/test_pvlan.py create mode 100644 cosmic-core/test/integration/smoke/test_quota.py create mode 100644 cosmic-core/test/integration/smoke/test_regions.py create mode 100644 cosmic-core/test/integration/smoke/test_reset_vm_on_reboot.py create mode 100644 cosmic-core/test/integration/smoke/test_resource_detail.py create mode 100644 cosmic-core/test/integration/smoke/test_router_dhcphosts.py create mode 100644 cosmic-core/test/integration/smoke/test_routers.py create mode 100644 cosmic-core/test/integration/smoke/test_routers_iptables_default_policy.py create mode 100644 cosmic-core/test/integration/smoke/test_routers_network_ops.py create mode 100644 cosmic-core/test/integration/smoke/test_scale_vm.py create mode 100644 cosmic-core/test/integration/smoke/test_secondary_storage.py create mode 100644 cosmic-core/test/integration/smoke/test_service_offerings.py create mode 100644 cosmic-core/test/integration/smoke/test_snapshots.py create mode 100644 cosmic-core/test/integration/smoke/test_ssvm.py create mode 100644 cosmic-core/test/integration/smoke/test_templates.py create mode 100644 cosmic-core/test/integration/smoke/test_usage_events.py create mode 100755 cosmic-core/test/integration/smoke/test_vm_life_cycle.py create mode 100644 cosmic-core/test/integration/smoke/test_vm_snapshots.py create mode 100644 cosmic-core/test/integration/smoke/test_volumes.py create mode 100644 cosmic-core/test/integration/smoke/test_vpc_redundant.py create mode 100644 cosmic-core/test/integration/smoke/test_vpc_router_nics.py create mode 100644 cosmic-core/test/integration/smoke/test_vpc_vpn.py create mode 100644 cosmic-core/test/integration/testpaths/__init__.py create mode 100644 cosmic-core/test/integration/testpaths/testpath_attach_disk_zwps.py create mode 100644 cosmic-core/test/integration/testpaths/testpath_custom_disk_offering.py create mode 100644 cosmic-core/test/integration/testpaths/testpath_multiple_snapshot.py create mode 100644 cosmic-core/test/integration/testpaths/testpath_queryAsyncJobResult.py create mode 100644 cosmic-core/test/integration/testpaths/testpath_revert_snap.py create mode 100644 cosmic-core/test/integration/testpaths/testpath_same_vm_name.py create mode 100755 cosmic-core/test/integration/testpaths/testpath_snapshot_hadrning.py create mode 100644 cosmic-core/test/integration/testpaths/testpath_snapshot_limits.py create mode 100644 cosmic-core/test/integration/testpaths/testpath_stopped_vm.py create mode 100644 cosmic-core/test/integration/testpaths/testpath_storage_migration.py create mode 100644 cosmic-core/test/integration/testpaths/testpath_usage.py create mode 100644 cosmic-core/test/integration/testpaths/testpath_uuid_event.py create mode 100755 cosmic-core/test/integration/testpaths/testpath_vmlc.py create mode 100644 cosmic-core/test/integration/testpaths/testpath_volume_cuncurrent_snapshots.py create mode 100644 cosmic-core/test/integration/testpaths/testpath_volume_recurring_snap.py create mode 100644 cosmic-core/test/integration/testpaths/testpath_volume_snapshot.py create mode 100644 cosmic-core/test/integration/testpaths/testpath_volumelifecycle.py create mode 100644 cosmic-core/test/metadata/adapter.xml create mode 100644 cosmic-core/test/metadata/delegatedAdmin/delegated_admin_cleanup.xml create mode 100644 cosmic-core/test/metadata/delegatedAdmin/delegated_admin_createusers.xml create mode 100644 cosmic-core/test/metadata/delegatedAdmin/delegated_admin_verify_part1.xml create mode 100644 cosmic-core/test/metadata/delegatedAdmin/delegated_admin_verify_part2.xml create mode 100644 cosmic-core/test/metadata/delegatedAdmin/pickuser_domainlevel1_domainlevel2.xml create mode 100644 cosmic-core/test/metadata/delegatedAdmin/pickuser_domainlevel1admin_domainlevel1admin.xml create mode 100644 cosmic-core/test/metadata/delegatedAdmin/pickuser_domainlevel1admin_rootadmin.xml create mode 100644 cosmic-core/test/metadata/delegatedAdmin/pickuser_domainlevel2_child_domainlevel1.xml create mode 100644 cosmic-core/test/metadata/delegatedAdmin/pickuser_domainlevel2_nonchild_domainlevel1.xml create mode 100644 cosmic-core/test/metadata/delegatedAdmin/pickuser_domainlevel2_rootadmin1.xml create mode 100644 cosmic-core/test/metadata/delegatedAdmin/pickuser_rootadmin1_rootadmin2.xml create mode 100644 cosmic-core/test/metadata/delegatedAdmin/pickuser_rootadmin_vs_domainlevel1admin.xml create mode 100644 cosmic-core/test/metadata/func/commands create mode 100644 cosmic-core/test/metadata/func/directnw_regression.xml create mode 100644 cosmic-core/test/metadata/func/error_events.properties create mode 100644 cosmic-core/test/metadata/func/expunge.xml create mode 100644 cosmic-core/test/metadata/func/external_firewall.xml create mode 100644 cosmic-core/test/metadata/func/flatnetwork.xml create mode 100644 cosmic-core/test/metadata/func/ha.xml create mode 100644 cosmic-core/test/metadata/func/iso.xml create mode 100644 cosmic-core/test/metadata/func/loadbalancers.xml create mode 100644 cosmic-core/test/metadata/func/localstorage_volume_test.xml create mode 100644 cosmic-core/test/metadata/func/mgmtvmsync.xml create mode 100644 cosmic-core/test/metadata/func/portforwarding.xml create mode 100644 cosmic-core/test/metadata/func/private_templates.xml create mode 100644 cosmic-core/test/metadata/func/regression.xml create mode 100644 cosmic-core/test/metadata/func/regression_events.properties create mode 100644 cosmic-core/test/metadata/func/regression_new.xml create mode 100644 cosmic-core/test/metadata/func/regression_test.xml create mode 100644 cosmic-core/test/metadata/func/regression_user.xml create mode 100644 cosmic-core/test/metadata/func/regression_works.xml create mode 100644 cosmic-core/test/metadata/func/resource_limits.xml create mode 100644 cosmic-core/test/metadata/func/roughflatstress.xml create mode 100644 cosmic-core/test/metadata/func/roughregression.xml create mode 100644 cosmic-core/test/metadata/func/sanity.xml create mode 100644 cosmic-core/test/metadata/func/securitygroups.xml create mode 100644 cosmic-core/test/metadata/func/sharedstorage_volume_test.xml create mode 100644 cosmic-core/test/metadata/func/snapshot_iso.xml create mode 100644 cosmic-core/test/metadata/func/snapshots.xml create mode 100644 cosmic-core/test/metadata/func/snapshots_contd.xml create mode 100644 cosmic-core/test/metadata/func/srxstresswithportfwd.xml create mode 100644 cosmic-core/test/metadata/func/static_nat.xml create mode 100644 cosmic-core/test/metadata/func/templatedwnldstress.xml create mode 100644 cosmic-core/test/metadata/func/templates_sync.xml create mode 100644 cosmic-core/test/metadata/func/userapi.xml create mode 100644 cosmic-core/test/metadata/func/vmapi.xml create mode 100644 cosmic-core/test/metadata/func/vmsync.xml create mode 100644 cosmic-core/test/pom.xml create mode 100755 cosmic-core/test/scripts/bootstrap-regression.sh create mode 100755 cosmic-core/test/scripts/build-env.sh create mode 100755 cosmic-core/test/scripts/certDeleteEC2.sh create mode 100755 cosmic-core/test/scripts/certSubmitEC2.sh create mode 100755 cosmic-core/test/scripts/checkLog.sh create mode 100755 cosmic-core/test/scripts/checkOutOfMemory.sh create mode 100755 cosmic-core/test/scripts/cleanparallel.sh create mode 100755 cosmic-core/test/scripts/deploy-and-run-regression.sh create mode 100755 cosmic-core/test/scripts/deploy.sh create mode 100755 cosmic-core/test/scripts/deploycluster.sh create mode 100755 cosmic-core/test/scripts/executeUserAPI.sh create mode 100755 cosmic-core/test/scripts/invoke.sh create mode 100755 cosmic-core/test/scripts/regression.sh create mode 100644 cosmic-core/test/scripts/run.bat create mode 100755 cosmic-core/test/scripts/run.sh create mode 100755 cosmic-core/test/scripts/script_lock_test/test.sh create mode 100755 cosmic-core/test/scripts/script_lock_test/test_task.sh create mode 100755 cosmic-core/test/scripts/sign.sh create mode 100755 cosmic-core/test/scripts/signEC2.sh create mode 100755 cosmic-core/test/scripts/usage/allocated.sh create mode 100755 cosmic-core/test/scripts/usage/network.sh create mode 100755 cosmic-core/test/scripts/usage/running.sh create mode 100755 cosmic-core/test/scripts/usage/volume_usage.sh create mode 100644 cosmic-core/test/scripts/usercloud.properties create mode 100755 cosmic-core/test/scripts/xen/corrupttemplate.sh create mode 100755 cosmic-core/test/scripts/xen/createfaketemplate.sh create mode 100755 cosmic-core/test/scripts/xen/killvm.sh create mode 100755 cosmic-core/test/scripts/xen/listtemplate.sh create mode 100755 cosmic-core/test/scripts/xen/listvdi.sh create mode 100755 cosmic-core/test/scripts/xen/listvm.sh create mode 100755 cosmic-core/test/scripts/xen/ms.sh create mode 100755 cosmic-core/test/scripts/xen/removetemplate.sh create mode 100755 cosmic-core/test/scripts/xen/shutdown.sh create mode 100755 cosmic-core/test/scripts/xen/sleep.sh create mode 100755 cosmic-core/test/scripts/xen/ssh.sh create mode 100644 cosmic-core/test/selenium/ReadMe.txt create mode 100644 cosmic-core/test/selenium/browser/__init__.py create mode 100644 cosmic-core/test/selenium/browser/firefox.py create mode 100644 cosmic-core/test/selenium/common/Global_Locators.py create mode 100644 cosmic-core/test/selenium/common/__init__.py create mode 100644 cosmic-core/test/selenium/common/shared.py create mode 100644 cosmic-core/test/selenium/cspages/__init__.py create mode 100644 cosmic-core/test/selenium/cspages/accounts/accountspage.py create mode 100644 cosmic-core/test/selenium/cspages/accounts/userspage.py create mode 100644 cosmic-core/test/selenium/cspages/cspage.py create mode 100644 cosmic-core/test/selenium/cspages/dashboard/__init__.py create mode 100644 cosmic-core/test/selenium/cspages/dashboard/dashboardpage.py create mode 100644 cosmic-core/test/selenium/cspages/login/__init__.py create mode 100644 cosmic-core/test/selenium/cspages/login/loginpage.py create mode 100644 cosmic-core/test/selenium/cstests/__init__.py create mode 100644 cosmic-core/test/selenium/cstests/regressiontests/__init__.py create mode 100644 cosmic-core/test/selenium/cstests/smoketests/__init__.py create mode 100644 cosmic-core/test/selenium/cstests/smoketests/adduser_test.py create mode 100644 cosmic-core/test/selenium/cstests/smoketests/adduseraccount_test.py create mode 100644 cosmic-core/test/selenium/cstests/smoketests/deleteuser_test.py create mode 100644 cosmic-core/test/selenium/cstests/smoketests/deleteuseraccount_test.py create mode 100644 cosmic-core/test/selenium/cstests/smoketests/global_settings_test.py create mode 100644 cosmic-core/test/selenium/cstests/smoketests/login_logout_as_JohnD_test.py create mode 100644 cosmic-core/test/selenium/cstests/smoketests/login_logout_test.py create mode 100644 cosmic-core/test/selenium/cstests/smoketests/navigation_test.py create mode 100644 cosmic-core/test/selenium/cstests/smoketests/smokecfg.py create mode 100644 cosmic-core/test/selenium/lib/Global_Locators.py create mode 100644 cosmic-core/test/selenium/lib/initialize.py create mode 100644 cosmic-core/test/selenium/smoke/Login_and_Accounts.py create mode 100644 cosmic-core/test/selenium/smoke/Service_Offering.py create mode 100644 cosmic-core/test/selenium/smoke/TemplatesAndISO.py create mode 100644 cosmic-core/test/selenium/smoke/VM_lifeCycle.py create mode 100644 cosmic-core/test/selenium/smoke/main.py create mode 100644 cosmic-core/test/src/test/java/com/cloud/sample/UserCloudAPIExecutor.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/longrun/BuildGuestNetwork.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/longrun/GuestNetwork.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/longrun/PerformanceWithAPI.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/longrun/User.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/longrun/VirtualMachine.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/regression/ApiCommand.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/regression/ConfigTest.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/regression/DelegatedAdminTest.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/regression/Deploy.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/regression/EventsApiTest.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/regression/HA.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/regression/LoadBalancingTest.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/regression/PortForwardingTest.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/regression/SanityTest.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/regression/Test.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/regression/TestCase.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/regression/TestCaseEngine.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/regression/VMApiTest.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/stress/SshTest.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/stress/StressTestDirectAttach.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/stress/TestClientWithAPI.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/stress/WgetTest.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/ui/AbstractSeleniumTestCase.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/ui/AddAndDeleteAISO.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/ui/AddAndDeleteATemplate.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/ui/UIScenarioTest.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/utils/ConsoleProxy.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/utils/IpSqlGenerator.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/utils/ProxyLoadTemp.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/utils/SignEC2.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/utils/SignRequest.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/utils/SqlDataGenerator.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/utils/SubmitCert.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/utils/TestClient.java create mode 100644 cosmic-core/test/src/test/java/com/cloud/test/utils/UtilsForTest.java create mode 100644 cosmic-core/test/systemvm/README.md create mode 100644 cosmic-core/test/systemvm/__init__.py create mode 100644 cosmic-core/test/systemvm/test_hello_systemvm.py create mode 100644 cosmic-core/test/systemvm/test_update_config.py create mode 100644 cosmic-core/tools/apidoc/XmlToHtmlConverter.java create mode 100755 cosmic-core/tools/apidoc/build-apidoc.sh create mode 100644 cosmic-core/tools/apidoc/gen_toc.py create mode 100644 cosmic-core/tools/apidoc/generateadmincommands.xsl create mode 100644 cosmic-core/tools/apidoc/generatecommand.xsl create mode 100644 cosmic-core/tools/apidoc/generatecustomcommand.xsl create mode 100644 cosmic-core/tools/apidoc/generatedomainadmincommands.xsl create mode 100644 cosmic-core/tools/apidoc/generategenericcommand.xsl create mode 100644 cosmic-core/tools/apidoc/generatetoc.xsl create mode 100644 cosmic-core/tools/apidoc/generatetoc_footer.xsl create mode 100644 cosmic-core/tools/apidoc/generatetoc_header.xsl create mode 100644 cosmic-core/tools/apidoc/generateusercommands.xsl create mode 100644 cosmic-core/tools/apidoc/images/api_bullets.gif create mode 100644 cosmic-core/tools/apidoc/images/back_button.gif create mode 100644 cosmic-core/tools/apidoc/images/back_button_hover.gif create mode 100644 cosmic-core/tools/apidoc/images/cloudstack.png create mode 100644 cosmic-core/tools/apidoc/includes/main.css create mode 100644 cosmic-core/tools/apidoc/pom.xml create mode 100644 cosmic-core/tools/pom.xml create mode 100644 cosmic-core/tools/transifex/.tx/config create mode 100644 cosmic-core/tools/transifex/README-transifex.txt create mode 100755 cosmic-core/tools/transifex/sync-transifex-ui.sh create mode 100644 cosmic-core/tools/whisker/LICENSE create mode 100644 cosmic-core/tools/whisker/NOTICE create mode 100644 cosmic-core/tools/whisker/descriptor-for-packaging.xml create mode 100644 cosmic-core/tools/whisker/descriptor.xml create mode 100644 cosmic-core/usage/conf/db.properties.in create mode 100644 cosmic-core/usage/conf/log4j-cloud_usage.xml.in create mode 100644 cosmic-core/usage/pom.xml create mode 100644 cosmic-core/usage/src/main/java/com/cloud/usage/StorageTypes.java create mode 100644 cosmic-core/usage/src/main/java/com/cloud/usage/UsageManager.java create mode 100644 cosmic-core/usage/src/main/java/com/cloud/usage/UsageManagerImpl.java create mode 100644 cosmic-core/usage/src/main/java/com/cloud/usage/UsageSanityChecker.java create mode 100644 cosmic-core/usage/src/main/java/com/cloud/usage/UsageServer.java create mode 100644 cosmic-core/usage/src/main/java/com/cloud/usage/parser/IPAddressUsageParser.java create mode 100644 cosmic-core/usage/src/main/java/com/cloud/usage/parser/LoadBalancerUsageParser.java create mode 100644 cosmic-core/usage/src/main/java/com/cloud/usage/parser/NetworkOfferingUsageParser.java create mode 100644 cosmic-core/usage/src/main/java/com/cloud/usage/parser/NetworkUsageParser.java create mode 100644 cosmic-core/usage/src/main/java/com/cloud/usage/parser/PortForwardingUsageParser.java create mode 100644 cosmic-core/usage/src/main/java/com/cloud/usage/parser/SecurityGroupUsageParser.java create mode 100644 cosmic-core/usage/src/main/java/com/cloud/usage/parser/StorageUsageParser.java create mode 100644 cosmic-core/usage/src/main/java/com/cloud/usage/parser/UsageParser.java create mode 100644 cosmic-core/usage/src/main/java/com/cloud/usage/parser/VMInstanceUsageParser.java create mode 100644 cosmic-core/usage/src/main/java/com/cloud/usage/parser/VMSnapshotUsageParser.java create mode 100644 cosmic-core/usage/src/main/java/com/cloud/usage/parser/VPNUserUsageParser.java create mode 100644 cosmic-core/usage/src/main/java/com/cloud/usage/parser/VmDiskUsageParser.java create mode 100644 cosmic-core/usage/src/main/java/com/cloud/usage/parser/VolumeUsageParser.java create mode 100644 cosmic-core/usage/src/main/resources/usageApplicationContext.xml create mode 100644 cosmic-core/usage/src/test/java/com/cloud/usage/UsageManagerTest.java create mode 100644 cosmic-core/usage/src/test/java/com/cloud/usage/UsageManagerTestConfiguration.java create mode 100644 cosmic-core/usage/src/test/java/com/cloud/usage/UsageSanityCheckerIT.java create mode 100644 cosmic-core/usage/src/test/java/com/cloud/usage/UsageSanityCheckerTest.java create mode 100644 cosmic-core/usage/src/test/resources/UsageManagerTestContext.xml create mode 100644 cosmic-core/usage/src/test/resources/cloud1.xml create mode 100644 cosmic-core/usage/src/test/resources/cloud2.xml create mode 100644 cosmic-core/usage/src/test/resources/cloud3.xml create mode 100644 cosmic-core/usage/src/test/resources/cloud_usage1.xml create mode 100644 cosmic-core/usage/src/test/resources/cloud_usage2.xml create mode 100644 cosmic-core/usage/src/test/resources/cloud_usage3.xml create mode 100644 cosmic-core/usage/src/test/resources/db.properties create mode 100644 cosmic-core/utils/bindir/cloud-gitrevs.in create mode 100755 cosmic-core/utils/bindir/cloud-sccs.in create mode 100644 cosmic-core/utils/conf/db.properties create mode 100644 cosmic-core/utils/conf/log4j-vmops.xml create mode 100755 cosmic-core/utils/pom.xml create mode 100644 cosmic-core/utils/src/main/java/com/cloud/exception/InvalidParameterValueException.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/maint/Version.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/ActionDelegate.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/AutoCloseableUtil.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/CloudResourceBundle.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/ConstantTimeComparator.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/DateUtil.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/EncryptionUtil.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/EnumUtils.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/ExecutionResult.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/FileUtil.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/HttpUtils.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/IteratorUtil.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/Journal.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/LogUtils.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/MethodCapturer.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/NumbersUtil.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/Pair.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/PasswordGenerator.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/Predicate.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/ProcessUtil.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/Profiler.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/PropertiesUtil.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/ReflectUtil.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/ReflectionUse.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/SerialVersionUID.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/StringUtils.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/SwiftUtil.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/Ternary.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/UriUtils.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/UsernamePasswordValidator.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/UuidUtils.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/backoff/BackoffAlgorithm.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/backoff/impl/ConstantTimeBackoff.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/backoff/impl/ConstantTimeBackoffMBean.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/cisco/n1kv/vsm/NetconfHelper.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/cisco/n1kv/vsm/PolicyMap.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/cisco/n1kv/vsm/VsmCommand.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/cisco/n1kv/vsm/VsmOkResponse.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/cisco/n1kv/vsm/VsmPolicyMapResponse.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/cisco/n1kv/vsm/VsmResponse.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/component/Adapter.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/component/AdapterBase.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/component/AdapterList.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/component/ComponentContext.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/component/ComponentInstantiationPostProcessor.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/component/ComponentLifecycle.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/component/ComponentLifecycleBase.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/component/ComponentMethodInterceptable.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/component/ComponentMethodInterceptor.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/component/ComponentNamingPolicy.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/component/Manager.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/component/ManagerBase.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/component/Named.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/component/PluggableService.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/component/Registry.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/component/SystemIntegrityChecker.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/concurrency/NamedThreadFactory.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/concurrency/Scheduler.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/concurrency/SynchronizationEvent.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/concurrency/TestClock.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/crypt/DBEncryptionUtil.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/crypt/EncryptionSecretKeyChecker.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/crypt/EncryptionSecretKeySender.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/crypt/RSAHelper.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/db/DbProperties.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/db/EntityManager.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/db/UUIDManager.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/encoding/URLEncoder.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/events/EventArgs.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/events/SubscriptionMgr.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/exception/CSExceptionErrorCode.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/exception/CloudRuntimeException.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/exception/ErrorContext.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/exception/ExceptionProxyObject.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/exception/ExceptionUtil.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/exception/ExecutionException.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/exception/HypervisorVersionChangedException.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/exception/NioConnectionException.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/exception/TaskExecutionException.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/fsm/ChangeEvent.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/fsm/FiniteState.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/fsm/FiniteState2.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/fsm/FiniteStateObject.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/fsm/NoTransitionException.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/fsm/State.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/fsm/StateDao.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/fsm/StateListener.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/fsm/StateMachine.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/fsm/StateMachine2.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/fsm/StateObject.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/log/CglibThrowableRenderer.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/mgmt/JmxUtil.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/mgmt/ManagementBean.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/mgmt/PropertyMapDynamicBean.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/net/HTTPUtils.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/net/Ip.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/net/Ip4Address.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/net/MacAddress.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/net/NetUtils.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/net/NfsUtils.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/net/Proxy.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/net/UrlUtil.java create mode 100755 cosmic-core/utils/src/main/java/com/cloud/utils/nicira/nvp/plugin/NiciraNvpApiVersion.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/nio/HandlerFactory.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/nio/Link.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/nio/NioClient.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/nio/NioConnection.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/nio/NioServer.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/nio/Task.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/nio/TrustAllManager.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/rest/BasicRestClient.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/rest/CloudstackRESTException.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/rest/HttpClientHelper.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/rest/HttpConstants.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/rest/HttpMethods.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/rest/HttpStatusCodeHelper.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/rest/HttpUriRequestBuilder.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/rest/RESTServiceConnector.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/rest/RestClient.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/script/OutputInterpreter.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/script/Script.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/script/Script2.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/security/CertificateHelper.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/ssh/SSHCmdHelper.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/ssh/SSHKeysHelper.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/ssh/SshException.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/ssh/SshHelper.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/storage/QCOW2Utils.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/storage/S3/ClientOptions.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/storage/S3/FileNamingStrategy.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/storage/S3/ObjectNamingStrategy.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/storage/S3/S3Utils.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/storage/encoding/DecodedDataObject.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/storage/encoding/DecodedDataStore.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/storage/encoding/Decoder.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/storage/encoding/EncodingType.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/time/InaccurateClock.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/time/InaccurateClockMBean.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/xmlobject/XmlObject.java create mode 100644 cosmic-core/utils/src/main/java/com/cloud/utils/xmlobject/XmlObjectParser.java create mode 100644 cosmic-core/utils/src/main/java/org/apache/cloudstack/utils/graphite/GraphiteClient.java create mode 100644 cosmic-core/utils/src/main/java/org/apache/cloudstack/utils/graphite/GraphiteException.java create mode 100644 cosmic-core/utils/src/main/java/org/apache/cloudstack/utils/hypervisor/HypervisorUtils.java create mode 100644 cosmic-core/utils/src/main/java/org/apache/cloudstack/utils/identity/ManagementServerNode.java create mode 100644 cosmic-core/utils/src/main/java/org/apache/cloudstack/utils/imagestore/ImageStoreUtil.java create mode 100644 cosmic-core/utils/src/main/java/org/apache/cloudstack/utils/security/SSLUtils.java create mode 100644 cosmic-core/utils/src/main/java/org/apache/cloudstack/utils/security/SecureSSLSocketFactory.java create mode 100644 cosmic-core/utils/src/main/java/org/apache/cloudstack/utils/usage/UsageUtils.java create mode 100644 cosmic-core/utils/src/main/java/org/apache/commons/httpclient/contrib/ssl/EasySSLProtocolSocketFactory.java create mode 100644 cosmic-core/utils/src/main/java/org/apache/commons/httpclient/contrib/ssl/EasyX509TrustManager.java create mode 100644 cosmic-core/utils/src/main/resources/cloud.keystore create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/DateUtilTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/DummyImpl.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/DummyInterface.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/DummyPremiumImpl.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/HttpUtilsTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/NumbersUtilTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/PasswordGeneratorTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/ProcessUtilTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/PropertiesUtilsTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/ReflectUtilTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/ScriptTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/StringUtilsTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/TernaryTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/TestProfiler.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/UriUtilsTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/UuidUtilsTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/backoff/impl/ConstantTimeBackoffTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/crypto/EncryptionSecretKeyCheckerTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/crypto/RSAHelperTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/encoding/UrlEncoderTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/exception/ExceptionUtilTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/log/CglibThrowableRendererTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/net/Ip4AddressTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/net/IpTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/net/MacAddressTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/net/NetUtilsTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/rest/BasicRestClientTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/rest/HttpClientHelperTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/rest/HttpRequestMatcher.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/rest/HttpStatusCodeHelperTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestBuilderTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestMethodMatcher.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestPathMatcher.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestPayloadMatcher.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestQueryMatcher.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/rest/RESTServiceConnectorTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/ssh/SSHKeysHelperTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/ssh/SshHelperTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/storage/QCOW2UtilsTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/testcase/Log4jEnabledTestCase.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/testcase/NioTest.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/xmlobject/TestXmlObject.java create mode 100644 cosmic-core/utils/src/test/java/com/cloud/utils/xmlobject/TestXmlObject2.java create mode 100644 cosmic-core/utils/src/test/java/org/apache/cloudstack/utils/hypervisor/HypervisorUtilsTest.java create mode 100644 cosmic-core/utils/src/test/java/org/apache/cloudstack/utils/imagestore/ImageStoreUtilTest.java create mode 100644 cosmic-core/utils/src/test/resources/com/cloud/utils/QualifierTestContext.xml create mode 100644 cosmic-core/utils/src/test/resources/com/cloud/utils/db/transactionContextBuilderTest.xml create mode 100755 cosmic-core/utils/src/test/resources/log4j.xml create mode 100644 cosmic-core/utils/src/test/resources/testContext.xml create mode 100644 cosmic-plugin-event-bus-rabbitmq/LICENSE create mode 100644 cosmic-plugin-event-bus-rabbitmq/README.md create mode 100644 cosmic-plugin-event-bus-rabbitmq/pom.xml create mode 100644 cosmic-plugin-event-bus-rabbitmq/src/main/java/org/apache/cloudstack/mom/rabbitmq/RabbitMQEventBus.java create mode 100644 cosmic-plugin-event-bus-rabbitmq/src/test/java/org/apache/cloudstack/mom/rabbitmq/RabbitMQEventBusTest.java create mode 100644 cosmic-plugin-hypervisor-kvm/LICENSE create mode 100644 cosmic-plugin-hypervisor-kvm/README.md create mode 100644 cosmic-plugin-hypervisor-kvm/pom.xml create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/ha/KvmInvestigator.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/BridgeVifDriver.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KvmGuestOsMapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KvmHaBase.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KvmHaChecker.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KvmHaMonitor.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtCapXmlParser.java create mode 100755 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtConnection.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXmlParser.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtNetworkDef.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtSecretDef.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolDef.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolXmlParser.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtStorageVolumeDef.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtStorageVolumeXmlParser.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVmDef.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtXmlParser.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/MigrateKvmAsync.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/OvsVifDriver.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/VifDriver.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/VifDriverBase.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtAttachIsoCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtBackupSnapshotCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckConsoleProxyLoadCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckHealthCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckNetworkCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckOnHostCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckSshCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckVirtualMachineCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCleanupNetworkRulesCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConsoleProxyLoadCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCopyVolumeCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreateCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreatePrivateTemplateFromSnapshotCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreatePrivateTemplateFromVolumeCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreateStoragePoolCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreateVolumeFromSnapshotCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtDeleteStoragePoolCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtDestroyCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtFenceCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetHostStatsCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetStorageStatsCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmDiskStatsCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmStatsCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVncPortCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMaintainCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtManageSnapshotCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtModifySshKeysCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtModifyStoragePoolCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtNetworkElementCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtNetworkRulesSystemVmCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtNetworkRulesVmSecondaryIpCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtNetworkUsageCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPingTestCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPlugNicCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrimaryStorageDownloadCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPvlanSetupCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReadyCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRebootCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRebootRouterCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRequestWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtResizeVolumeCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevertSnapshotCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSecurityGroupRulesCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStopCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStorageSubSystemCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUnPlugNicCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUpdateHostPasswordCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUpgradeSnapshotCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUtilitiesHelper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtWatchConsoleProxyLoadCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/storage/IscsiAdmStorageAdaptor.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/storage/IscsiAdmStoragePool.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KvmPhysicalDisk.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KvmStoragePool.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KvmStoragePoolManager.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KvmStorageProcessor.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KvmStorageResource.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KvmVirtualDisk.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ManagedNfsStorageAdaptor.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/storage/StorageAdaptor.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/com/cloud/hypervisor/kvm/storage/StorageAdaptorInfo.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/org/apache/cloudstack/utils/linux/CpuStat.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/org/apache/cloudstack/utils/linux/MemStat.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImgException.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImgFile.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/resources/META-INF/cloudstack/kvm-compute/module.properties create mode 100644 cosmic-plugin-hypervisor-kvm/src/main/resources/META-INF/cloudstack/kvm-compute/spring-kvm-compute-context.xml create mode 100644 cosmic-plugin-hypervisor-kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParserTest.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtSecretDefTest.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolDefTest.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVifDriverTest.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUtilitiesHelperTest.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/test/java/com/cloud/hypervisor/kvm/storage/KvmPhysicalDiskTest.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/test/java/com/cloud/hypervisor/kvm/storage/KvmStorageProcessorTest.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/test/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePoolTest.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/test/java/org/apache/cloudstack/utils/linux/MemStatTest.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgFileTest.java create mode 100644 cosmic-plugin-hypervisor-kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java create mode 100644 cosmic-plugin-hypervisor-ovm3/LICENSE create mode 100644 cosmic-plugin-hypervisor-ovm3/README.md create mode 100644 cosmic-plugin-hypervisor-ovm3/pom.xml create mode 100644 cosmic-plugin-hypervisor-ovm3/sonar-project.properties create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/ha/Ovm3Investigator.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/objects/CloudstackPlugin.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/objects/Cluster.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/objects/Common.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/objects/Connection.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/objects/Linux.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/objects/Network.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/objects/Ntp.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/objects/Ovm3ResourceException.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/objects/OvmObject.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/objects/Pool.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/objects/PoolOcfS2.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/objects/Remote.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/objects/Repository.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/objects/RpcTypeFactory.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/objects/StoragePlugin.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/objects/Xen.java create mode 100755 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/resources/Ovm3Discoverer.java create mode 100755 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/resources/Ovm3FenceBuilder.java create mode 100755 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/resources/Ovm3HypervisorGuru.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/resources/Ovm3HypervisorResource.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/resources/Ovm3StorageProcessor.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/resources/Ovm3VirtualRoutingResource.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/resources/helpers/Ovm3Configuration.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/resources/helpers/Ovm3HypervisorNetwork.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/resources/helpers/Ovm3HypervisorSupport.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/resources/helpers/Ovm3StoragePool.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/resources/helpers/Ovm3VirtualRoutingSupport.java create mode 100755 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/resources/helpers/Ovm3VmGuestTypes.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/java/com/cloud/hypervisor/ovm3/resources/helpers/Ovm3VmSupport.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/resources/META-INF/cloudstack/ovm3-compute/module.properties create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/resources/META-INF/cloudstack/ovm3-compute/spring-ovm3-compute-context.xml create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/resources/META-INF/cloudstack/ovm3-discoverer/module.properties create mode 100644 cosmic-plugin-hypervisor-ovm3/src/main/resources/META-INF/cloudstack/ovm3-discoverer/spring-ovm3-discoverer-context.xml create mode 100644 cosmic-plugin-hypervisor-ovm3/src/test/java/com/cloud/hypervisor/ovm3/objects/CloudStackPluginTest.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/test/java/com/cloud/hypervisor/ovm3/objects/CommonTest.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/test/java/com/cloud/hypervisor/ovm3/objects/ConnectionTest.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/test/java/com/cloud/hypervisor/ovm3/objects/LinuxTest.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/test/java/com/cloud/hypervisor/ovm3/objects/NetworkTest.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/test/java/com/cloud/hypervisor/ovm3/objects/NtpTest.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/test/java/com/cloud/hypervisor/ovm3/objects/PoolOCFS2Test.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/test/java/com/cloud/hypervisor/ovm3/objects/PoolTest.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/test/java/com/cloud/hypervisor/ovm3/objects/RemoteTest.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/test/java/com/cloud/hypervisor/ovm3/objects/RepositoryTest.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/test/java/com/cloud/hypervisor/ovm3/objects/StoragePluginTest.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/test/java/com/cloud/hypervisor/ovm3/objects/XenTest.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/test/java/com/cloud/hypervisor/ovm3/objects/XmlTestResultTest.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/test/java/com/cloud/hypervisor/ovm3/resources/Ovm3HypervisorResourceTest.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/test/java/com/cloud/hypervisor/ovm3/resources/Ovm3StorageProcessorTest.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/test/java/com/cloud/hypervisor/ovm3/resources/Ovm3VirtualRoutingResourceTest.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/test/java/com/cloud/hypervisor/ovm3/resources/helpers/Ovm3ConfigurationTest.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/test/java/com/cloud/hypervisor/ovm3/resources/helpers/Ovm3GuestTypesTest.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/test/java/com/cloud/hypervisor/ovm3/resources/helpers/Ovm3HypervisorNetworkTest.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/test/java/com/cloud/hypervisor/ovm3/resources/helpers/Ovm3HypervisorSupportTest.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/test/java/com/cloud/hypervisor/ovm3/resources/helpers/Ovm3VirtualRoutingSupportTest.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/test/java/com/cloud/hypervisor/ovm3/resources/helpers/Ovm3VmSupportTest.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/test/java/com/cloud/hypervisor/ovm3/support/Ovm3SupportTest.java create mode 100644 cosmic-plugin-hypervisor-ovm3/src/test/resources/log4j.properties create mode 100755 cosmic-plugin-hypervisor-ovm3/src/test/resources/scripts/clean_master.sh create mode 100755 cosmic-plugin-hypervisor-ovm3/src/test/resources/scripts/clean_slave.sh create mode 100755 cosmic-plugin-hypervisor-ovm3/src/test/resources/scripts/create_pool_cluster.py create mode 100755 cosmic-plugin-hypervisor-ovm3/src/test/resources/scripts/info.py create mode 100755 cosmic-plugin-hypervisor-ovm3/src/test/resources/scripts/password.py create mode 100755 cosmic-plugin-hypervisor-ovm3/src/test/resources/scripts/repo_pool.py create mode 100755 cosmic-plugin-hypervisor-ovm3/src/test/resources/scripts/simple_pool.py create mode 100755 cosmic-plugin-hypervisor-ovm3/src/test/resources/scripts/socat.sh create mode 100755 cosmic-plugin-hypervisor-ovm3/src/test/resources/scripts/tail.sh create mode 100644 cosmic-plugin-hypervisor-xenserver/LICENSE create mode 100644 cosmic-plugin-hypervisor-xenserver/README.md create mode 100644 cosmic-plugin-hypervisor-xenserver/pom.xml create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/ha/XenServerFencer.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/XenServerGuru.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/discoverer/XcpServerDiscoverer.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixHelper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/XcpOssResource.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/XcpServerResource.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/XenServer56FP1Resource.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/XenServer56Resource.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/XenServer56SP2Resource.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/XenServer600Resource.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/XenServer610Resource.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/XenServer620Resource.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/XenServer620SP1Resource.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/XenServer650Resource.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/XenServerConnectionPool.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/XenServerStorageProcessor.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/Xenserver625Resource.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/Xenserver625StorageProcessor.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/XsHost.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/XsLocalNetwork.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xcp/XcpServerNetworkUsageCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xen56/XenServer56CheckOnHostCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xen56/XenServer56FenceCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xen56/XenServer56NetworkUsageCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xen56p1/XenServer56FP1FenceCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xen610/XenServer610MigrateVolumeCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xen610/XenServer610MigrateWithStorageCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xen610/XenServer610MigrateWithStorageCompleteCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xen610/XenServer610MigrateWithStorageReceiveCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xen610/XenServer610MigrateWithStorageSendCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xen620sp1/XenServer620SP1GetGPUStatsCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixAttachIsoCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixAttachOrDettachConfigDriveCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCheckConsoleProxyLoadCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCheckHealthCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCheckNetworkCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCheckOnHostCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCheckSshCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCheckVirtualMachineCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCleanupNetworkRulesCmdWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixClusterVMMetaDataSyncCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixConsoleProxyLoadCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCreateCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCreateStoragePoolCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCreateVMSnapshotCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixDeleteStoragePoolCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixDeleteVMSnapshotCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixDestroyCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixGetHostStatsCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixGetStorageStatsCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixGetVmDiskStatsCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixGetVmIpAddressCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixGetVmStatsCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixGetVncPortCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixMaintainCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixMigrateCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixModifySshKeysCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixModifyStoragePoolCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixNetworkElementCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixNetworkRulesSystemVmCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixNetworkRulesVmSecondaryIpCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixPerformanceMonitorCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixPingTestCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixPlugNicCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixPrepareForMigrationCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixPrimaryStorageDownloadCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixPvlanSetupCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixReadyCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixRebootCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixRebootRouterCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixRequestWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixResizeVolumeCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixRevertToVMSnapshotCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixScaleVmCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixSecurityGroupRulesCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixSetupCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixStartCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixStopCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixStorageSubSystemCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixUnPlugNicCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixUpdateHostPasswordCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixUpgradeSnapshotCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixWatchConsoleProxyLoadCommandWrapper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServerUtilitiesHelper.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/org/apache/cloudstack/hypervisor/xenserver/XenServerResourceNewBase.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/org/apache/cloudstack/hypervisor/xenserver/XenserverConfigs.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/java/org/apache/cloudstack/storage/motion/XenServerStorageMotionStrategy.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/resources/META-INF/cloudstack/xenserver-compute/module.properties create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/resources/META-INF/cloudstack/xenserver-compute/spring-xenserver-compute-context.xml create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/resources/META-INF/cloudstack/xenserver-discoverer/module.properties create mode 100644 cosmic-plugin-hypervisor-xenserver/src/main/resources/META-INF/cloudstack/xenserver-discoverer/spring-xenserver-discoverer-context.xml create mode 100644 cosmic-plugin-hypervisor-xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/CitrixHelperTest.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBaseTest.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XcpOssResourceTest.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XcpServerResourceTest.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer56FP1ResourceTest.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer56ResourceTest.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer56SP2ResourceTest.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer600ResourceTest.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer625ResourceTest.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/XenServer650ResourceTest.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixRequestWrapperTest.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XcpServerWrapperTest.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer56FP1WrapperTest.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer56WrapperTest.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer610WrapperTest.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer620SP1WrapperTest.java create mode 100644 cosmic-plugin-hypervisor-xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/XenServer620WrapperTest.java create mode 100644 cosmic-plugin-user-authenticator-ldap/README.md create mode 100644 cosmic-plugin-user-authenticator-ldap/pom.xml create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/api/command/LDAPConfigCmd.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/api/command/LDAPRemoveCmd.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/api/command/LdapAddConfigurationCmd.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/api/command/LdapCreateAccountCmd.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/api/command/LdapDeleteConfigurationCmd.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/api/command/LdapImportUsersCmd.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/api/command/LdapListConfigurationCmd.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/api/command/LdapListUsersCmd.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/api/command/LdapUserSearchCmd.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/api/command/LinkDomainToLdapCmd.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/api/response/LDAPConfigResponse.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/api/response/LDAPRemoveResponse.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/api/response/LdapConfigurationResponse.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/api/response/LdapUserResponse.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/api/response/LinkDomainToLdapResponse.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/ldap/ADLdapUserManagerImpl.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/ldap/DistinguishedNameParser.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/ldap/LdapAuthenticator.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/ldap/LdapConfiguration.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/ldap/LdapConfigurationVO.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/ldap/LdapContextFactory.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/ldap/LdapManager.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/ldap/LdapManagerImpl.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/ldap/LdapTrustMapVO.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/ldap/LdapUser.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/ldap/LdapUserManager.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/ldap/LdapUserManagerFactory.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/ldap/LdapUtils.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/ldap/NoLdapUserMatchingQueryException.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/ldap/NoSuchLdapUserException.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/ldap/OpenLdapUserManagerImpl.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/ldap/dao/LdapConfigurationDao.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/ldap/dao/LdapConfigurationDaoImpl.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/ldap/dao/LdapTrustMapDao.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/java/org/apache/cloudstack/ldap/dao/LdapTrustMapDaoImpl.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/resources/META-INF/cloudstack/ldap/module.properties create mode 100644 cosmic-plugin-user-authenticator-ldap/src/main/resources/META-INF/cloudstack/ldap/spring-ldap-context.xml create mode 100644 cosmic-plugin-user-authenticator-ldap/src/test/java/groovy/org/apache/cloudstack/ldap/ADLdapUserManagerImplSpec.groovy create mode 100644 cosmic-plugin-user-authenticator-ldap/src/test/java/groovy/org/apache/cloudstack/ldap/BasicNamingEnumerationImpl.groovy create mode 100644 cosmic-plugin-user-authenticator-ldap/src/test/java/groovy/org/apache/cloudstack/ldap/LdapAddConfigurationCmdSpec.groovy create mode 100644 cosmic-plugin-user-authenticator-ldap/src/test/java/groovy/org/apache/cloudstack/ldap/LdapAuthenticatorSpec.groovy create mode 100644 cosmic-plugin-user-authenticator-ldap/src/test/java/groovy/org/apache/cloudstack/ldap/LdapConfigurationDaoImplSpec.groovy create mode 100644 cosmic-plugin-user-authenticator-ldap/src/test/java/groovy/org/apache/cloudstack/ldap/LdapConfigurationResponseSpec.groovy create mode 100644 cosmic-plugin-user-authenticator-ldap/src/test/java/groovy/org/apache/cloudstack/ldap/LdapConfigurationSpec.groovy create mode 100644 cosmic-plugin-user-authenticator-ldap/src/test/java/groovy/org/apache/cloudstack/ldap/LdapConfigurationVOSpec.groovy create mode 100644 cosmic-plugin-user-authenticator-ldap/src/test/java/groovy/org/apache/cloudstack/ldap/LdapContextFactorySpec.groovy create mode 100644 cosmic-plugin-user-authenticator-ldap/src/test/java/groovy/org/apache/cloudstack/ldap/LdapCreateAccountCmdSpec.groovy create mode 100644 cosmic-plugin-user-authenticator-ldap/src/test/java/groovy/org/apache/cloudstack/ldap/LdapDeleteConfigurationCmdSpec.groovy create mode 100644 cosmic-plugin-user-authenticator-ldap/src/test/java/groovy/org/apache/cloudstack/ldap/LdapImportUsersCmdSpec.groovy create mode 100644 cosmic-plugin-user-authenticator-ldap/src/test/java/groovy/org/apache/cloudstack/ldap/LdapListConfigurationCmdSpec.groovy create mode 100644 cosmic-plugin-user-authenticator-ldap/src/test/java/groovy/org/apache/cloudstack/ldap/LdapListUsersCmdSpec.groovy create mode 100644 cosmic-plugin-user-authenticator-ldap/src/test/java/groovy/org/apache/cloudstack/ldap/LdapManagerImplSpec.groovy create mode 100644 cosmic-plugin-user-authenticator-ldap/src/test/java/groovy/org/apache/cloudstack/ldap/LdapSearchUserCmdSpec.groovy create mode 100644 cosmic-plugin-user-authenticator-ldap/src/test/java/groovy/org/apache/cloudstack/ldap/LdapUserManagerFactorySpec.groovy create mode 100644 cosmic-plugin-user-authenticator-ldap/src/test/java/groovy/org/apache/cloudstack/ldap/LdapUserResponseSpec.groovy create mode 100644 cosmic-plugin-user-authenticator-ldap/src/test/java/groovy/org/apache/cloudstack/ldap/LdapUserSpec.groovy create mode 100644 cosmic-plugin-user-authenticator-ldap/src/test/java/groovy/org/apache/cloudstack/ldap/LdapUtilsSpec.groovy create mode 100644 cosmic-plugin-user-authenticator-ldap/src/test/java/groovy/org/apache/cloudstack/ldap/LinkDomainToLdapCmdSpec.groovy create mode 100644 cosmic-plugin-user-authenticator-ldap/src/test/java/groovy/org/apache/cloudstack/ldap/NoLdapUserMatchingQueryExceptionSpec.groovy create mode 100644 cosmic-plugin-user-authenticator-ldap/src/test/java/groovy/org/apache/cloudstack/ldap/NoSuchLdapUserExceptionSpec.groovy create mode 100644 cosmic-plugin-user-authenticator-ldap/src/test/java/groovy/org/apache/cloudstack/ldap/OpenLdapUserManagerSpec.groovy create mode 100644 cosmic-plugin-user-authenticator-ldap/src/test/java/org/apache/cloudstack/ldap/DistinguishedNameParserTest.java create mode 100644 cosmic-plugin-user-authenticator-ldap/src/test/resources/cosmic.cloud.ldif create mode 100644 cosmic-plugin-user-authenticator-sha256salted/README.md create mode 100644 cosmic-plugin-user-authenticator-sha256salted/pom.xml create mode 100644 cosmic-plugin-user-authenticator-sha256salted/src/main/java/com/cloud/server/auth/SHA256SaltedUserAuthenticator.java create mode 100644 cosmic-plugin-user-authenticator-sha256salted/src/main/resources/META-INF/cloudstack/sha256salted/module.properties create mode 100644 cosmic-plugin-user-authenticator-sha256salted/src/main/resources/META-INF/cloudstack/sha256salted/spring-sha256salted-context.xml create mode 100644 cosmic-plugin-user-authenticator-sha256salted/src/test/java/src/com/cloud/server/auth/test/AuthenticatorTest.java create mode 100644 pom.xml diff --git a/.gitignore b/.gitignore index 32858aad3c..2fd9c607b4 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,458 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* + +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +*.iml + +## Directory-based project format: +.idea/ + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Eclipse project settings +.project +.settings/ + +# RPM packaging +dist/ + +# Management Server logs +api.log +vmops.log* + +# Packaging directory +packaging/ +cosmic-packaging/ + +#PyDev settings +.pydevproject + +target/ +# Eclipse project files + +.classpath +.project +.settings/ +target/ + +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +*.iml + +## Directory-based project format: +.idea/ + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Checkstyle settings +.checkstyle +# Eclipse project files + +.classpath +.project +.settings/ +target/ + +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +*.iml + +## Directory-based project format: +.idea/ + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Checkstyle settings +.checkstyle +# 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. + +build/build.number +.lock-wscript +.waf-* +waf-* +target/ +override/ +.metadata +dist/ +*~ +*.bak +cloud-*.tar.bz2 +*.log +*.pyc +*.egginfo/ +*.egg-info/ +*.prefs +build.number +*.log.*.gz +cloud.log.*.* +unittest +deps/cloud.userlibraries +.DS_Store +.idea +*.iml +git-remote-https.exe.stackdump +*.swp +tools/cli/cloudmonkey/marvin/ +tools/cli/cloudmonkey/precache.py +tools/marvin/marvin/cloudstackAPI/ +tools/marvin/build/ +tools/cli/build/ +*.jar +*.war +*.mar +*.zip +*.iso +*.tar.gz +*.tgz + +# this ignores _all files starting with '.'. Don't do that! +#.* + +target-eclipse +!.gitignore +.classpath +.settings.xml +.settings/ +db.properties.override +replace.properties.override +tools/marvin/marvin/cloudstackAPI/* +*.egg-info/ +docs/tmp +docs/publish +docs/runbook/tmp +docs/runbook/publish +.project +Gemfile.lock +debian/tmp +debian/files +debian/cloudstack-*/* +debian/*.substvars +debian/*.debhelper +replace.properties.tmp +build-indep-stamp +configure-stamp +*_flymake.js +engine/storage/integration-test/test-output +tools/apidoc/log/ +plugins/network-elements/juniper-contrail/logs/ +scripts/vm/hypervisor/xenserver/vhd-util +*.orig +tools/appliance/box/ +.reviewboardrc +.checkstyle +.pmd +.pmdruleset.xml +.pydevproject +systemvm/.pydevproject +test/.pydevprojec +**/failed_plus_exceptions.txt +**/runinfo.txt +**/.cache/ +**/*.iml +# 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. + +/AddConfigCmd.java +/UpdateZoneCmd.java +/target +# Eclipse project files + +.classpath +.project +.settings/ +target/ + +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +*.iml + +## Directory-based project format: +.idea/ + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Checkstyle settings +.checkstyle +# Eclipse project files + +.classpath +.project +.settings/ +target/ + +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +*.iml + +## Directory-based project format: +.idea/ + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Checkstyle settings +.checkstyle +# Eclipse project files + +.classpath +.project +.settings/ +target/ + +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +*.iml + +## Directory-based project format: +.idea/ + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Checkstyle settings +.checkstyle +# Eclipse project files + +.classpath +.project +.settings/ +target/ + +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +*.iml + +## Directory-based project format: +.idea/ + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Checkstyle settings +.checkstyle +# Eclipse project files + +.classpath +.project +.settings/ +target/ + +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +*.iml + +## Directory-based project format: +.idea/ + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Checkstyle settings +.checkstyle +# Eclipse project files + +.classpath +.project +.settings/ +target/ + +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +*.iml + +## Directory-based project format: +.idea/ + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Checkstyle settings +.checkstyle diff --git a/LICENSE b/LICENSE index 8dada3edaf..8f71f43fee 100644 --- a/LICENSE +++ b/LICENSE @@ -199,3 +199,4 @@ 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. + diff --git a/README.md b/README.md index 434a8d7ebd..d51acd9495 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,165 @@ -# blackhole -Cosmic Cloud Single Repository +# Cosmic +Cosmic is open source software designed to deploy and manage large +networks of virtual machines, as a highly available, highly scalable +Infrastructure as a Service (IaaS) cloud computing platform. Cosmic +provides an on-premises (private) cloud offering, or as part of a +hybrid cloud solution. + +Cosmic is a turnkey solution that includes the entire "stack" of features +most organizations want with an cloud: compute orchestration, +Network-as-a-Service, user and account management, a full and open native API, +resource accounting, and a first-class User Interface (UI). + +Cosmic currently supports the following hypervisors: +KVM and XenServer. +Support for other hypervisors can be added if contributors can provide the infrastructure to test agains. + +Users can manage their cloud via Web interface, command line +tools, and/or a full-featured query based API. + +## Getting Source Repository + +Cosmic officials Git repository is located at: + + https://github.com/missioncriticalcloud/cosmic + +## Building from Source + +Cosmic requires: +- Java 8 +- GitHub account with an [enabled SSH key] (https://help.github.com/articles/adding-a-new-ssh-key-to-your-github-account/) because you need to clone over SSH to get all the submodules +- Maven settings configured to use [Cosmic's Nexus repository](https://beta-nexus.mcc.schubergphilis.com) (see [Maven settings](#maven-settings) bellow) + +In order to build Cosmic, you have to follow the steps below: + + git clone --recursive git@github.com:MissionCriticalCloud/cosmic.git + cd cosmic + mvn clean install -P developer,systemvm + +The steps above will build the essentials to get Cosmic management server working. Besides that, you will also need a hypervisor. See our [build stream configuration](https://beta-jenkins.mcc.schubergphilis.com) for more details. + + cd cosmic-client + mvn -pl :cloud-client-ui jetty:run + +Go to your brouwser and type: [http://localhost:8080/client] (http://localhost:8080/client) + +### Maven settings + +Configure maven to look for artefacts in [Cosmic's Nexus repository](https://beta-nexus.mcc.schubergphilis.com): + +```vim ~/.m2/settings.xml ``` + +```xml + + + + + beta-nexus + * + https://beta-nexus.mcc.schubergphilis.com/content/groups/public + + + + + beta-nexus + + + + + central + http://central + true + true + + + + + central + http://central + true + true + + + + + +``` +Either enable the `beta-nexus` profile on the command line, or make it enabled by default in your settings file. +```xml +... + + + beta-nexus + +... +``` + +## Building RPM Packages + +In order to build Cosmic RPM packages, please refer to the [Packaging repository](https://github.com/MissionCriticalCloud/packaging) README section. + +## Links + +Cosmic is a fork of Apache CloudStack and its API is backwards compatible with CloudStack's API. So, all the documentation can be accessed from: + +* [Documentation](http://docs.cloudstack.apache.org) +* [API documentation](http://cloudstack.apache.org/docs/api) + +## Getting Involved + +Please, join our Slack channel for more details: + +* [Mission Critical Cloud](https://missioncriticalcloud.slack.com) + +## Reporting Security Vulnerabilities + +If you've found an issue that you believe is a security vulnerability in a +released version of Cosmic, please report it to `int-cloud@schubergphilis.com` with details about the vulnerability, how it +might be exploited, and any additional information that might be useful. + +## License + +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. + +Please see the [LICENSE](LICENSE) file included in the root directory +of the source tree for extended license details. + +## Notice of Cryptographic Software + +This distribution includes cryptographic software. The country in which you currently +reside may have restrictions on the import, possession, use, and/or re-export to another +country, of encryption software. BEFORE using any encryption software, please check your +country's laws, regulations and policies concerning the import, possession, or use, and +re-export of encryption software, to see if this is permitted. See http://www.wassenaar.org/ +for more information. + +The U.S. Government Department of Commerce, Bureau of Industry and Security (BIS), has +classified this software as Export Commodity Control Number (ECCN) 5D002.C.1, which +includes information security software using or performing cryptographic functions with +asymmetric algorithms. The form and manner of this Apache Software Foundation distribution +makes it eligible for export under the License Exception ENC Technology Software +Unrestricted (TSU) exception (see the BIS Export Administration Regulations, Section +740.13) for both object code and source code. + +The following provides more details on the included cryptographic software: + +* Cosmic makes use of JaSypt cryptographic libraries +* Cosmic has a system requirement of MySQL, and uses native database encryption functionality. +* Cosmic makes use of the Bouncy Castle general-purpose encryption library. +* Cosmic can optionally interacts with and controls OpenSwan-based VPNs. +* Cosmic has a dependency on and makes use of JSch - a java SSH2 implementation. diff --git a/build/replace.properties b/build/replace.properties new file mode 100644 index 0000000000..862a0d17ab --- /dev/null +++ b/build/replace.properties @@ -0,0 +1,33 @@ +DBUSER=cloud +DBPW=cloud +DBROOTPW= +APISERVERLOG=api.log +DBHOST=localhost +COMPONENTS-SPEC=components.xml +REMOTEHOST=localhost +AGENTCLASSPATH= +AGENTLOG=/var/log/cosmic/agent/agent.log +AGENTLOGDIR=/var/log/cosmic/agent/ +AGENTSYSCONFDIR=/etc/cosmic/agent +BINDIR=/usr/bin +COMMONLIBDIR=/usr/share/cosmic-common +IPALOCATORLOG=/var/log/cosmic/management/ipallocator.log +JAVADIR=/usr/share/java +LIBEXECDIR=/usr/libexec +LOCKDIR=/var/lock +MSCLASSPATH= +MSCONF=/etc/cosmic/management +MSENVIRON=/usr/share/cosmic-management +MSLOG=/var/log/cosmic/management/management-server.log +MSLOGDIR=/var/log/cosmic/management/ +MSMNTDIR=/var/cosmic/mnt +MSUSER=cloud +PIDDIR=/var/run +PLUGINJAVADIR=/usr/share/cosmic-management/plugin +PREMIUMJAVADIR=/usr/share/cosmic-management/premium +PYTHONDIR=/usr/lib/python2.7/site-packages/ +SERVERSYSCONFDIR=/etc/sysconfig +SETUPDATADIR=/usr/share/cosmic-management/setup +SYSCONFDIR=/etc/sysconfig +USAGELOG=/var/log/cosmic/usage/usage.log +USAGESYSCONFDIR=/etc/sysconfig diff --git a/cosmic-agent/LICENSE b/cosmic-agent/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/cosmic-agent/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/cosmic-agent/README.md b/cosmic-agent/README.md new file mode 100644 index 0000000000..79b6842309 --- /dev/null +++ b/cosmic-agent/README.md @@ -0,0 +1 @@ +# cosmic-agent \ No newline at end of file diff --git a/cosmic-agent/bindir/cloud-setup-agent.in b/cosmic-agent/bindir/cloud-setup-agent.in new file mode 100755 index 0000000000..ff3b85de65 --- /dev/null +++ b/cosmic-agent/bindir/cloud-setup-agent.in @@ -0,0 +1,153 @@ +#!/usr/bin/python +# 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. + +import os +import logging +import sys +import socket +from cloudutils.cloudException import CloudRuntimeException, CloudInternalException +from cloudutils.utilities import initLoging, bash +from cloudutils.configFileOps import configFileOps +from cloudutils.globalEnv import globalEnv +from cloudutils.networkConfig import networkConfig +from cloudutils.syscfg import sysConfigFactory + +from optparse import OptionParser + +def getUserInputs(): + print "Welcome to the CloudStack Agent Setup:" + + cfo = configFileOps("@AGENTSYSCONFDIR@/agent.properties") + oldMgt = cfo.getEntry("host") + + mgtSvr = raw_input("Please input the Management Server Hostname/IP-Address:[%s]"%oldMgt) + if mgtSvr == "": + mgtSvr = oldMgt + try: + socket.getaddrinfo(mgtSvr, 443) + except: + print "Failed to resolve %s. Please input a valid hostname or IP-Address."%mgtSvr + exit(1) + + oldToken = cfo.getEntry("zone") + zoneToken = raw_input("Please input the Zone Id:[%s]"%oldToken) + + if zoneToken == "": + zoneToken = oldToken + + oldPod = cfo.getEntry("pod") + podId = raw_input("Please input the Pod Id:[%s]"%oldPod) + + if podId == "": + podId = oldToken + + oldCluster = cfo.getEntry("cluster") + clusterId = raw_input("Please input the Cluster Id:[%s]"%oldCluster) + if clusterId == "": + clusterId = oldCluster + + oldHypervisor = cfo.getEntry("hypervisor") + if oldHypervisor == "": + oldHypervisor = "kvm" + + hypervisor = raw_input("Please input the Hypervisor type kvm:[%s]"%oldHypervisor) + if hypervisor == "": + hypervisor = oldHypervisor + + try: + defaultNic = networkConfig.getDefaultNetwork() + except: + print "Failed to get default route. Please configure your network to have a default route" + exit(1) + + defNic = defaultNic.name + network = raw_input("Please choose which network used to create VM:[%s]"%defNic) + if network == "": + if defNic == "": + print "You need to specifiy one of Nic or bridge on your system" + exit(1) + elif network == "": + network = defNic + + return [mgtSvr, zoneToken, network, podId, clusterId, hypervisor] + +if __name__ == '__main__': + initLoging("@AGENTLOGDIR@/setup.log") + glbEnv = globalEnv() + + glbEnv.mode = "Agent" + glbEnv.agentMode = "Agent" + parser = OptionParser() + parser.add_option("-a", action="store_true", dest="auto", help="auto mode") + parser.add_option("-m", "--host", dest="mgt", help="Management server hostname or IP-Address") + parser.add_option("-z", "--zone", dest="zone", help="zone id") + parser.add_option("-p", "--pod", dest="pod", help="pod id") + parser.add_option("-c", "--cluster", dest="cluster", help="cluster id") + parser.add_option("-t", "--hypervisor", default="kvm", dest="hypervisor", help="hypervisor type") + parser.add_option("-g", "--guid", dest="guid", help="guid") + parser.add_option("--pubNic", dest="pubNic", help="Public traffic interface") + parser.add_option("--prvNic", dest="prvNic", help="Private traffic interface") + parser.add_option("--guestNic", dest="guestNic", help="Guest traffic interface") + + old_config = configFileOps("@AGENTSYSCONFDIR@/agent.properties") + bridgeType = old_config.getEntry("network.bridge.type").lower() + if bridgeType: + glbEnv.bridgeType = bridgeType + + (options, args) = parser.parse_args() + if options.auto is None: + userInputs = getUserInputs() + glbEnv.mgtSvr = userInputs[0] + glbEnv.zone = userInputs[1] + glbEnv.defaultNic = userInputs[2] + glbEnv.pod = userInputs[3] + glbEnv.cluster = userInputs[4] + glbEnv.hypervisor = userInputs[5] + #generate UUID + glbEnv.uuid = old_config.getEntry("guid") + if glbEnv.uuid == "": + glbEnv.uuid = bash("uuidgen").getStdout() + else: + for para, value in options.__dict__.items(): + if value is None: + print "Missing operand:%s"%para + print "Try %s --help for more information"%sys.argv[0] + sys.exit(1) + + glbEnv.uuid = options.guid + glbEnv.mgtSvr = options.mgt + glbEnv.zone = options.zone + glbEnv.pod = options.pod + glbEnv.cluster = options.cluster + glbEnv.hypervisor = options.hypervisor + glbEnv.nics.append(options.prvNic) + glbEnv.nics.append(options.pubNic) + glbEnv.nics.append(options.guestNic) + + print "Starting to configure your system:" + syscfg = sysConfigFactory.getSysConfigFactory(glbEnv) + try: + syscfg.config() + print "CloudStack Agent setup is done!" + except (CloudRuntimeException,CloudInternalException), e: + print e + print "Try to restore your system:" + try: + syscfg.restore() + except: + pass diff --git a/cosmic-agent/bindir/cloud-ssh.in b/cosmic-agent/bindir/cloud-ssh.in new file mode 100644 index 0000000000..e4b3c141a9 --- /dev/null +++ b/cosmic-agent/bindir/cloud-ssh.in @@ -0,0 +1,19 @@ +#!/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. + +ssh -i /root/.ssh/id_rsa.cloud -p 3922 root@$1 diff --git a/cosmic-agent/bindir/cosmic-agent-profile.sh.in b/cosmic-agent/bindir/cosmic-agent-profile.sh.in new file mode 100644 index 0000000000..93b10b3f32 --- /dev/null +++ b/cosmic-agent/bindir/cosmic-agent-profile.sh.in @@ -0,0 +1,20 @@ +#!/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. + +# need access to lsmod for adding host as non-root +PATH=$PATH:/sbin diff --git a/cosmic-agent/bindir/cosmic-agent-upgrade.in b/cosmic-agent/bindir/cosmic-agent-upgrade.in new file mode 100644 index 0000000000..72b0fae585 --- /dev/null +++ b/cosmic-agent/bindir/cosmic-agent-upgrade.in @@ -0,0 +1,63 @@ +#!/usr/bin/python +# 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. +from cloudutils.networkConfig import networkConfig +from cloudutils.utilities import bash +import logging +import re +def isOldStyleBridge(brName): + if brName.find("cloudVirBr") == 0: + return True + else: + return False +def upgradeBridgeName(brName, enslavedDev): + print("upgrade bridge: %s, %s"%(brName, enslavedDev)) + vlanId = brName.replace("cloudVirBr", "") + print("find vlan Id: %s"%vlanId) + phyDev = enslavedDev.split(".")[0] + print("find physical device %s"%phyDev) + newBrName = "br" + phyDev + "-" + vlanId + print("new bridge name %s"%newBrName) + bash("ip link set %s down"%brName) + bash("ip link set %s name %s"%(brName, newBrName)) + bash("ip link set %s up" %newBrName) + cmd = "iptables-save | grep FORWARD | grep -w " + brName + rules = bash(cmd).stdout.split('\n') + rules.pop() + for rule in rules: + try: + delrule = re.sub("-A", "-D", rule) + newrule = re.sub(" " + brName + " ", " " + newBrName + " ", rule) + bash("iptables " + delrule) + bash("iptables " + newrule) + except: + logging.exception("Ignoring failure to update rules for rule " + rule + " on bridge " + brName) +if __name__ == '__main__': + netlib = networkConfig() + bridges = netlib.listNetworks() + bridges = filter(isOldStyleBridge, bridges) + for br in bridges: + enslavedDev = netlib.getEnslavedDev(br, 1) + if enslavedDev is not None: + upgradeBridgeName(br, enslavedDev) + + bridges = netlib.listNetworks() + bridges = filter(isOldStyleBridge, bridges) + if len(bridges) > 0: + print("Warning: upgrade is not finished, still some bridges have the old style name:" + str(bridges)) + else: + print("Upgrade succeed") diff --git a/cosmic-agent/bindir/libvirtqemuhook.in b/cosmic-agent/bindir/libvirtqemuhook.in new file mode 100755 index 0000000000..1326303d51 --- /dev/null +++ b/cosmic-agent/bindir/libvirtqemuhook.in @@ -0,0 +1,62 @@ +#!/usr/bin/python +# 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. +import sys +import re +from xml.dom.minidom import parse +from cloudutils.configFileOps import configFileOps +from cloudutils.networkConfig import networkConfig +def isOldStyleBridge(brName): + if brName.find("cloudVirBr") == 0: + return True + else: + return False +def isNewStyleBridge(brName): + if re.match(r"br(\w+)-(\d+)", brName) == None: + return False + else: + return True +def getGuestNetworkDevice(): + netlib = networkConfig() + cfo = configFileOps("/etc/cosmic/agent/agent.properties") + guestDev = cfo.getEntry("guest.network.device") + enslavedDev = netlib.getEnslavedDev(guestDev, 1) + return enslavedDev.split(".")[0] +def handleMigrateBegin(): + try: + domain = parse(sys.stdin) + for interface in domain.getElementsByTagName("interface"): + source = interface.getElementsByTagName("source")[0] + bridge = source.getAttribute("bridge") + if isOldStyleBridge(bridge): + vlanId = bridge.replace("cloudVirBr","") + elif isNewStyleBridge(bridge): + vlanId = re.sub(r"br(\w+)-","",bridge) + else: + continue + phyDev = getGuestNetworkDevice() + newBrName="br" + phyDev + "-" + vlanId + source.setAttribute("bridge", newBrName) + print(domain.toxml()) + except: + pass +if __name__ == '__main__': + if len(sys.argv) != 5: + sys.exit(0) + + if sys.argv[2] == "migrate" and sys.argv[3] == "begin": + handleMigrateBegin() diff --git a/cosmic-agent/conf/agent.properties b/cosmic-agent/conf/agent.properties new file mode 100644 index 0000000000..1b344dc4ff --- /dev/null +++ b/cosmic-agent/conf/agent.properties @@ -0,0 +1,175 @@ +# 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. + +# Sample configuration file for CloudStack agent + +# The GUID to identify the agent with, this is mandatory! +# Generate with "uuidgen" +guid= + +#resource= the java class, which agent load to execute +resource=com.cloud.hypervisor.kvm.resource.LibvirtComputingResource + +#workers= number of threads running in agent +workers=5 + +#host= The IP address of management server +host=localhost + +#port = The port management server listening on, default is 8250 +port=8250 + +#cluster= The cluster which the agent belongs to +cluster=default + +#pod= The pod which the agent belongs to +pod=default + +#zone= The zone which the agent belongs to +zone=default + +#public.network.device= the public nic device +# if this is commented, it is autodetected on service startup +# public.network.device=cloudbr0 + +#private.network.device= the private nic device +# if this is commented, it is autodetected on service startup +# private.network.device=cloudbr1 + +#guest.network.device= the guest nic device +# if this is commented, the private nic device will be used + +# local storage path, by default, it's /var/lib/libvirt/images/ +#local.storage.path=/var/lib/libvirt/images/ + +# The UUID for the local storage pool, this is mandatory! +# Generate with "uuidgen" +local.storage.uuid= + +# Location for KVM scripts +domr.scripts.dir=scripts/network/domr/kvm + +# the timeout for time-consuming operations, such as create/copy snapshot +#cmds.timeout=7200 + +# set the vm migrate speed, by default, it will try to guess the speed of the guest network +# In MegaBytes per second +#vm.migrate.speed=0 + +# set target downtime at end of livemigration, the 'hiccup' for final copy. Higher numbers +# make livemigration easier, lower numbers may cause migration to never complete. Less than 1 +# means hypervisor default (20ms). +#vm.migrate.downtime=0 + +# Busy VMs may never finish migrating, depending on environment. When its available, we will +# want to add support for autoconvergence migration flag which should fix this. Set an upper +# limit in milliseconds for how long live migration should wait, at which point VM is paused and +# migration will finish quickly. Less than 1 means disabled. +#vm.migrate.pauseafter=0 + +# set the type of bridge used on the hypervisor, this defines what commands the resource +# will use to setup networking. Currently supported NATIVE, OPENVSWITCH +#network.bridge.type=native + +# set the driver used to plug and unplug nics from the bridges +# a sensible default will be selected based on the network.bridge.type but can +# be overridden here. +# native = com.cloud.hypervisor.kvm.resource.BridgeVifDriver +# openvswitch = com.cloud.hypervisor.kvm.resource.OvsVifDriver +#libvirt.vif.driver=com.cloud.hypervisor.kvm.resource.BridgeVifDriver + +# set the hypervisor type, values are: kvm +hypervisor.type=kvm + +# set the hypervisor URI. Usually there is no need for changing this +# For KVM: qemu:///system +# hypervisor.uri=qemu:///system + +# settings to enable direct networking in libvirt, should not be used +# on hosts that run system vms, values for mode are: private, bridge, vepa +# libvirt.vif.driver=com.cloud.hypervisor.kvm.resource.DirectVifDriver +# network.direct.source.mode=private +# network.direct.device=eth0 + +# setting to enable the cpu model to kvm guest globally. +# three option:custom,host-model and host-passthrough. +# custom - user custom the CPU model which specified by guest.cpu.model. +# host-model - identify the named CPU model which most closely matches the host, +# and then request additional CPU flags to complete the match. This should give +# close to maximum functionality/performance, which maintaining good +# reliability/compatibility if the guest is migrated to another host with slightly different host CPUs. +# host-passthrough - tell KVM to passthrough the host CPU with no modifications. +# The difference to host-model, instead of just matching feature flags, +# every last detail of the host CPU is matched. This gives absolutely best performance, +# and can be important to some apps which check low level CPU details, +# but it comes at a cost wrt migration. The guest can only be migrated to +# an exactly matching host CPU. +# +# guest.cpu.mode=custom|host-model|host-passthrough +# This param is only valid if guest.cpu.mode=custom, +# for examples:"Conroe" "Penryn", "Nehalem", "Westmere", "pentiumpro" and so +# on,run virsh capabilities for more details. +# guest.cpu.model= +# +# This param will require CPU features on the section +# guest.cpu.features=vmx vme +# +# vm.memballoon.disable=true +# Disable memory ballooning on vm guests for overcommit, by default overcommit +# feature enables balloon and sets currentMemory to a minimum value. +# +# vm.diskactivity.checkenabled=false +# Set to true to check disk activity on VM's disks before starting a VM. This only applies +# to QCOW2 files, and ensures that there is no other running instance accessing +# the file before starting. It works by checking the modify time against the current time, +# so care must be taken to ensure the cluster has time synced, otherwise VMs may fail to start. +# +# vm.diskactivity.checktimeout_s=120 +# Timeout for giving up on waiting for VM's disk files to become inactive. Hitting +# this timeout will result in failure to start VM. +# +# vm.diskactivity.inactivetime_ms=30000 +# This is the length of time that the disk needs to be inactive in order to pass the check. +# This means current time minus mtime of disk file needs to be greater than this number. +# It also has the side effect of setting the minimum threshold between a stop and start of +# a given VM. +# +# kvmclock.disable=false +# Some newer linux kernels are incapable of reliably migrating vms with kvmclock +# This is a workaround for the bug, admin can set this to true per-host +# +# vm.rng.enable=true +# This enabled the VirtIO Random Number Generator device for guests. +# +# vm.rng.model=random +# The model of VirtIO Random Number Generator (RNG) to present to the Guest. +# Currently only 'random' is supported. +# +# vm.rng.path=/dev/random +# Local Random Number Device Generator to use for VirtIO RNG for Guests. +# This is usually /dev/random, but per platform this might be different +# +# vm.watchdog.model=i6300esb +# The model of Watchdog timer to present to the Guest +# For all models refer to the libvirt documentation. +# Recommend value is: i6300esb +# +# vm.watchdog.action=none +# Action to take when the Guest/Instance is no longer notifiying the Watchdog +# timer. +# For all actions refer to the libvirt documentation. +# Recommended values are: none, reset and poweroff. diff --git a/cosmic-agent/conf/cosmic-agent.logrotate b/cosmic-agent/conf/cosmic-agent.logrotate new file mode 100644 index 0000000000..b66c2586e5 --- /dev/null +++ b/cosmic-agent/conf/cosmic-agent.logrotate @@ -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. + +/var/log/cosmic/agent/cosmic-agent.out +/var/log/cosmic/agent/cosmic-agent.err +{ + copytruncate + daily + rotate 5 + compress + missingok +} diff --git a/cosmic-agent/conf/developer.properties.template b/cosmic-agent/conf/developer.properties.template new file mode 100644 index 0000000000..a70a136f38 --- /dev/null +++ b/cosmic-agent/conf/developer.properties.template @@ -0,0 +1,55 @@ +# 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. + +#instance=AH +#private.macaddr.start=00:16:3e:77:01:01 +#private.ipaddr.start=192.168.166.128 + +#instance=KM +#private.macaddr.start=00:16:3e:77:02:01 +#private.ipaddr.start=192.168.167.128 + +#instance=KY +#private.macaddr.start=00:16:3e:77:03:01 +#private.ipaddr.start=192.168.168.128 + +#instance=WC +#private.macaddr.start=00:16:3e:77:04:01 +#private.ipaddr.start=192.168.169.128 + +#instance=CV +#private.macaddr.start=00:16:3e:77:05:01 +#private.ipaddr.start=192.168.170.128 + +#instance=KS +#private.macaddr.start=00:16:3e:77:06:01 +#private.ipaddr.start=192.168.171.128 + +#instance=ES +#private.macaddr.start=00:16:3e:77:07:01 +#private.ipaddr.start=192.168.172.128 + +#instance=RC +#private.macaddr.start=00:16:3e:77:08:01 +#private.ipaddr.start=192.168.173.128 + +#instance=AX +#private.macaddr.start=00:16:3e:77:09:01 +#private.ipaddr.start=192.168.174.128 + +private.macaddr.start=@private.macaddr.start@ +private.ipaddr.start=@private.ipaddr.start@ diff --git a/cosmic-agent/conf/environment.properties.in b/cosmic-agent/conf/environment.properties.in new file mode 100644 index 0000000000..514161a13f --- /dev/null +++ b/cosmic-agent/conf/environment.properties.in @@ -0,0 +1,21 @@ +# 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. + +# management server compile-time environment parameters + +paths.pid=@PIDDIR@ +paths.script=@COMMONLIBDIR@ diff --git a/cosmic-agent/conf/log4j-cloud.xml.in b/cosmic-agent/conf/log4j-cloud.xml.in new file mode 100644 index 0000000000..6bc80f0847 --- /dev/null +++ b/cosmic-agent/conf/log4j-cloud.xml.in @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cosmic-agent/pom.xml b/cosmic-agent/pom.xml new file mode 100644 index 0000000000..7766c1c826 --- /dev/null +++ b/cosmic-agent/pom.xml @@ -0,0 +1,117 @@ + + 4.0.0 + cloud-agent + Cosmic Agent + + + cloud.cosmic + cosmic + 5.1.0.0-SNAPSHOT + + + + scm:git:git@github.com:MissionCriticalCloud/cosmic-agent.git + scm:git:git@github.com:MissionCriticalCloud/cosmic-agent.git + HEAD + + + + + cloud.cosmic + cloud-nucleo + 5.1.0.0-SNAPSHOT + + + cloud.cosmic + cloud-utils + 5.1.0.0-SNAPSHOT + + + commons-io + commons-io + + + commons-daemon + commons-daemon + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.2 + + ${cs.jdk.version} + ${cs.jdk.version} + + + + maven-antrun-plugin + + + generate-resource + generate-resources + + run + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-dependencies + package + + copy-dependencies + + + ${project.build.directory}/dependencies + runtime + + + + + + + diff --git a/cosmic-agent/src/main/java/com/cloud/agent/Agent.java b/cosmic-agent/src/main/java/com/cloud/agent/Agent.java new file mode 100644 index 0000000000..8a1f33adc9 --- /dev/null +++ b/cosmic-agent/src/main/java/com/cloud/agent/Agent.java @@ -0,0 +1,859 @@ +// 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.agent; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.channels.ClosedChannelException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.naming.ConfigurationException; + +import com.cloud.agent.api.AgentControlAnswer; +import com.cloud.agent.api.AgentControlCommand; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.CronCommand; +import com.cloud.agent.api.MaintainAnswer; +import com.cloud.agent.api.MaintainCommand; +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; +import com.cloud.agent.transport.Request; +import com.cloud.agent.transport.Response; +import com.cloud.exception.AgentControlChannelException; +import com.cloud.resource.ServerResource; +import com.cloud.utils.PropertiesUtil; +import com.cloud.utils.backoff.BackoffAlgorithm; +import com.cloud.utils.concurrency.NamedThreadFactory; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.exception.NioConnectionException; +import com.cloud.utils.exception.TaskExecutionException; +import com.cloud.utils.nio.HandlerFactory; +import com.cloud.utils.nio.Link; +import com.cloud.utils.nio.NioClient; +import com.cloud.utils.nio.NioConnection; +import com.cloud.utils.nio.Task; +import com.cloud.utils.script.OutputInterpreter; +import com.cloud.utils.script.Script; + +import org.apache.cloudstack.managed.context.ManagedContextTimerTask; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +/** + * @config {@table + * || Param Name | Description | Values | Default || + * || type | Type of server | Storage / Computing / Routing | No Default || + * || workers | # of workers to process the requests | int | 1 || + * || host | host to connect to | ip address | localhost || + * || port | port to connect to | port number | 8250 || + * || instance | Used to allow multiple agents running on the same host | String | none || * } + *

+ * For more configuration options, see the individual types. + **/ +public class Agent implements HandlerFactory, IAgentControl { + private static final Logger s_logger = LoggerFactory.getLogger(Agent.class.getName()); + + public enum ExitStatus { + Normal(0), // Normal status = 0. + Upgrade(65), // Exiting for upgrade. + Configuration(66), // Exiting due to configuration problems. + Error(67); // Exiting because of error. + + int value; + + ExitStatus(final int value) { + this.value = value; + } + + public int value() { + return value; + } + } + + List _controlListeners = new ArrayList<>(); + + IAgentShell _shell; + NioConnection _connection; + ServerResource _resource; + Link _link; + Long _id; + + Timer _timer = new Timer("Agent Timer"); + + List _watchList = new ArrayList<>(); + long _sequence = 0; + long _lastPingResponseTime = 0; + long _pingInterval = 0; + AtomicInteger _inProgress = new AtomicInteger(); + + StartupTask _startup = null; + long _startupWaitDefault = 180000; + long _startupWait = _startupWaitDefault; + boolean _reconnectAllowed = true; + //For time sentitive task, e.g. PingTask + private final ThreadPoolExecutor _ugentTaskPool; + ExecutorService _executor; + + public Agent(final IAgentShell shell, final int localAgentId, final ServerResource resource) throws ConfigurationException { + _shell = shell; + _resource = resource; + _link = null; + + resource.setAgentControl(this); + + final String value = _shell.getPersistentProperty(getResourceName(), "id"); + _id = value != null ? Long.parseLong(value) : null; + s_logger.info("id is " + (_id != null ? _id : "")); + + final Map params = PropertiesUtil.toMap(_shell.getProperties()); + + // merge with properties from command line to let resource access command line parameters + for (final Map.Entry cmdLineProp : _shell.getCmdLineProperties().entrySet()) { + params.put(cmdLineProp.getKey(), cmdLineProp.getValue()); + } + + if (!_resource.configure(getResourceName(), params)) { + throw new ConfigurationException("Unable to configure " + _resource.getName()); + } + + _connection = new NioClient("Agent", _shell.getHost(), _shell.getPort(), _shell.getWorkers(), this); + + // ((NioClient)_connection).setBindAddress(_shell.getPrivateIp()); + + s_logger.debug("Adding shutdown hook"); + Runtime.getRuntime().addShutdownHook(new ShutdownThread(this)); + + _ugentTaskPool = + new ThreadPoolExecutor(shell.getPingRetries(), 2 * shell.getPingRetries(), 10, TimeUnit.MINUTES, new SynchronousQueue(), new NamedThreadFactory( + "UgentTask")); + + _executor = + new ThreadPoolExecutor(_shell.getWorkers(), 5 * _shell.getWorkers(), 1, TimeUnit.DAYS, new LinkedBlockingQueue(), new NamedThreadFactory( + "agentRequest-Handler")); + + s_logger.info("Agent [id = " + (_id != null ? _id : "new") + " : type = " + getResourceName() + " : zone = " + _shell.getZone() + " : pod = " + _shell.getPod() + + " : workers = " + _shell.getWorkers() + " : host = " + _shell.getHost() + " : port = " + _shell.getPort()); + } + + public String getVersion() { + return _shell.getVersion(); + } + + public String getResourceGuid() { + final String guid = _shell.getGuid(); + return guid + "-" + getResourceName(); + } + + public String getZone() { + return _shell.getZone(); + } + + public String getPod() { + return _shell.getPod(); + } + + protected void setLink(final Link link) { + _link = link; + } + + public ServerResource getResource() { + return _resource; + } + + public BackoffAlgorithm getBackoffAlgorithm() { + return _shell.getBackoffAlgorithm(); + } + + public String getResourceName() { + return _resource.getClass().getSimpleName(); + } + + public void start() { + if (!_resource.start()) { + s_logger.error("Unable to start the resource: " + _resource.getName()); + throw new CloudRuntimeException("Unable to start the resource: " + _resource.getName()); + } + + try { + _connection.start(); + } catch (final NioConnectionException e) { + s_logger.warn("NIO Connection Exception " + e); + s_logger.info("Attempted to connect to the server, but received an unexpected exception, trying again..."); + } + while (!_connection.isStartup()) { + _shell.getBackoffAlgorithm().waitBeforeRetry(); + _connection = new NioClient("Agent", _shell.getHost(), _shell.getPort(), _shell.getWorkers(), this); + try { + _connection.start(); + } catch (final NioConnectionException e) { + s_logger.warn("NIO Connection Exception " + e); + s_logger.info("Attempted to connect to the server, but received an unexpected exception, trying again..."); + } + } + } + + public void stop(final String reason, final String detail) { + s_logger.info("Stopping the agent: Reason = " + reason + (detail != null ? ": Detail = " + detail : "")); + if (_connection != null) { + final ShutdownCommand cmd = new ShutdownCommand(reason, detail); + try { + if (_link != null) { + final Request req = new Request(_id != null ? _id : -1, -1, cmd, false); + _link.send(req.toBytes()); + } + } catch (final ClosedChannelException e) { + s_logger.warn("Unable to send: " + cmd.toString()); + } catch (final Exception e) { + s_logger.warn("Unable to send: " + cmd.toString() + " due to exception: ", e); + } + s_logger.debug("Sending shutdown to management server"); + try { + Thread.sleep(1000); + } catch (final InterruptedException e) { + s_logger.debug("Who the heck interrupted me here?"); + } + _connection.stop(); + _connection = null; + } + + if (_resource != null) { + _resource.stop(); + _resource = null; + } + + _ugentTaskPool.shutdownNow(); + } + + public Long getId() { + return _id; + } + + public void setId(final Long id) { + s_logger.info("Set agent id " + id); + _id = id; + _shell.setPersistentProperty(getResourceName(), "id", Long.toString(id)); + } + + public void scheduleWatch(final Link link, final Request request, final long delay, final long period) { + synchronized (_watchList) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Adding a watch list"); + } + final WatchTask task = new WatchTask(link, request, this); + _timer.schedule(task, 0, period); + _watchList.add(task); + } + } + + protected void cancelTasks() { + synchronized (_watchList) { + for (final WatchTask task : _watchList) { + task.cancel(); + } + if (s_logger.isDebugEnabled()) { + s_logger.debug("Clearing watch list: " + _watchList.size()); + } + _watchList.clear(); + } + } + + public synchronized void lockStartupTask(final Link link) { + _startup = new StartupTask(link); + _timer.schedule(_startup, _startupWait); + } + + public void sendStartup(final Link link) { + final StartupCommand[] startup = _resource.initialize(); + if (startup != null) { + final Command[] commands = new Command[startup.length]; + for (int i = 0; i < startup.length; i++) { + setupStartupCommand(startup[i]); + commands[i] = startup[i]; + } + final Request request = new Request(_id != null ? _id : -1, -1, commands, false, false); + request.setSequence(getNextSequence()); + + if (s_logger.isDebugEnabled()) { + s_logger.debug("Sending Startup: " + request.toString()); + } + lockStartupTask(link); + try { + link.send(request.toBytes()); + } catch (final ClosedChannelException e) { + s_logger.warn("Unable to send reques: " + request.toString()); + } + } + } + + protected void setupStartupCommand(final StartupCommand startup) { + final InetAddress addr; + try { + addr = InetAddress.getLocalHost(); + } catch (final UnknownHostException e) { + s_logger.warn("unknow host? ", e); + throw new CloudRuntimeException("Cannot get local IP address"); + } + + final Script command = new Script("hostname", 500, s_logger); + final OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser(); + final String result = command.execute(parser); + final String hostname = result == null ? parser.getLine() : addr.toString(); + + startup.setId(getId()); + if (startup.getName() == null) { + startup.setName(hostname); + } + startup.setDataCenter(getZone()); + startup.setPod(getPod()); + startup.setGuid(getResourceGuid()); + startup.setResourceName(getResourceName()); + startup.setVersion(getVersion()); + } + + @Override + public Task create(final Task.Type type, final Link link, final byte[] data) { + return new ServerHandler(type, link, data); + } + + protected void reconnect(final Link link) { + if (!_reconnectAllowed) { + return; + } + synchronized (this) { + if (_startup != null) { + _startup.cancel(); + _startup = null; + } + } + + link.close(); + link.terminated(); + + setLink(null); + cancelTasks(); + + _resource.disconnected(); + + int inProgress = 0; + do { + _shell.getBackoffAlgorithm().waitBeforeRetry(); + + s_logger.info("Lost connection to the server. Dealing with the remaining commands..."); + + inProgress = _inProgress.get(); + if (inProgress > 0) { + s_logger.info("Cannot connect because we still have " + inProgress + " commands in progress."); + } + } while (inProgress > 0); + + _connection.stop(); + + try { + _connection.cleanUp(); + } catch (final IOException e) { + s_logger.warn("Fail to clean up old connection. " + e); + } + + while (_connection.isStartup()) { + _shell.getBackoffAlgorithm().waitBeforeRetry(); + } + + _connection = new NioClient("Agent", _shell.getHost(), _shell.getPort(), _shell.getWorkers(), this); + do { + s_logger.info("Reconnecting..."); + try { + _connection.start(); + } catch (final NioConnectionException e) { + s_logger.warn("NIO Connection Exception " + e); + s_logger.info("Attempted to connect to the server, but received an unexpected exception, trying again..."); + } + _shell.getBackoffAlgorithm().waitBeforeRetry(); + } while (!_connection.isStartup()); + s_logger.info("Connected to the server"); + } + + public void processStartupAnswer(final Answer answer, final Response response, final Link link) { + boolean cancelled = false; + synchronized (this) { + if (_startup != null) { + _startup.cancel(); + _startup = null; + } else { + cancelled = true; + } + } + final StartupAnswer startup = (StartupAnswer) answer; + if (!startup.getResult()) { + s_logger.error("Not allowed to connect to the server: " + answer.getDetails()); + System.exit(1); + } + if (cancelled) { + s_logger.warn("Threw away a startup answer because we're reconnecting."); + return; + } + + s_logger.info("Proccess agent startup answer, agent id = " + startup.getHostId()); + + setId(startup.getHostId()); + _pingInterval = (long) startup.getPingInterval() * 1000; // change to ms. + + setLastPingResponseTime(); + scheduleWatch(link, response, _pingInterval, _pingInterval); + + _ugentTaskPool.setKeepAliveTime(2 * _pingInterval, TimeUnit.MILLISECONDS); + + s_logger.info("Startup Response Received: agent id = " + getId()); + } + + protected void processRequest(final Request request, final Link link) { + boolean requestLogged = false; + Response response = null; + try { + final Command[] cmds = request.getCommands(); + final Answer[] answers = new Answer[cmds.length]; + + for (int i = 0; i < cmds.length; i++) { + final Command cmd = cmds[i]; + Answer answer; + try { + if (cmd.getContextParam("logid") != null) { + MDC.put("logcontextid", cmd.getContextParam("logid")); + } + if (s_logger.isDebugEnabled()) { + if (!requestLogged) // ensures request is logged only once per method call + { + final String requestMsg = request.toString(); + if (requestMsg != null) { + s_logger.debug("Request:" + requestMsg); + } + requestLogged = true; + } + s_logger.debug("Processing command: " + cmd.toString()); + } + + if (cmd instanceof CronCommand) { + final CronCommand watch = (CronCommand) cmd; + scheduleWatch(link, request, (long) watch.getInterval() * 1000, watch.getInterval() * 1000); + answer = new Answer(cmd, true, null); + } else if (cmd instanceof ShutdownCommand) { + final ShutdownCommand shutdown = (ShutdownCommand) cmd; + s_logger.debug("Received shutdownCommand, due to: " + shutdown.getReason()); + 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(); + _reconnectAllowed = false; + answer = new MaintainAnswer((MaintainCommand) cmd); + } else if (cmd instanceof AgentControlCommand) { + answer = null; + synchronized (_controlListeners) { + for (final IAgentControlListener listener : _controlListeners) { + answer = listener.processControlRequest(request, (AgentControlCommand) cmd); + if (answer != null) { + break; + } + } + } + + if (answer == null) { + s_logger.warn("No handler found to process cmd: " + cmd.toString()); + answer = new AgentControlAnswer(cmd); + } + + } else { + if (cmd instanceof ReadyCommand) { + processReadyCommand(cmd); + } + _inProgress.incrementAndGet(); + try { + answer = _resource.executeRequest(cmd); + } finally { + _inProgress.decrementAndGet(); + } + if (answer == null) { + s_logger.debug("Response: unsupported command" + cmd.toString()); + answer = Answer.createUnsupportedCommandAnswer(cmd); + } + } + } catch (final Throwable th) { + s_logger.warn("Caught: ", th); + final StringWriter writer = new StringWriter(); + th.printStackTrace(new PrintWriter(writer)); + answer = new Answer(cmd, false, writer.toString()); + } + + answers[i] = answer; + if (!answer.getResult() && request.stopOnError()) { + for (i++; i < cmds.length; i++) { + answers[i] = new Answer(cmds[i], false, "Stopped by previous failure"); + } + break; + } + } + response = new Response(request, answers); + } finally { + if (s_logger.isDebugEnabled()) { + final String responseMsg = response.toString(); + if (responseMsg != null) { + s_logger.debug(response.toString()); + } + } + + if (response != null) { + try { + link.send(response.toBytes()); + } catch (final ClosedChannelException e) { + s_logger.warn("Unable to send response: " + response.toString()); + } + } + } + } + + public void processResponse(final Response response, final Link link) { + final Answer answer = response.getAnswer(); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Received response: " + response.toString()); + } + if (answer instanceof StartupAnswer) { + processStartupAnswer(answer, response, link); + } else if (answer instanceof AgentControlAnswer) { + // Notice, we are doing callback while holding a lock! + synchronized (_controlListeners) { + for (final IAgentControlListener listener : _controlListeners) { + listener.processControlResponse(response, (AgentControlAnswer) answer); + } + } + } else { + setLastPingResponseTime(); + } + } + + public void processReadyCommand(final 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(final Task task) { + final Object obj = task.get(); + if (obj instanceof Response) { + if (System.currentTimeMillis() - _lastPingResponseTime > _pingInterval * _shell.getPingRetries()) { + s_logger.error("Ping Interval has gone past " + _pingInterval * _shell.getPingRetries() + ". Won't reconnect to mgt server, as connection is still alive"); + return; + } + + final PingCommand ping = _resource.getCurrentStatus(getId()); + final Request request = new Request(_id, -1, ping, false); + request.setSequence(getNextSequence()); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Sending ping: " + request.toString()); + } + + try { + task.getLink().send(request.toBytes()); + //if i can send pingcommand out, means the link is ok + setLastPingResponseTime(); + } 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(); + if (command.getContextParam("logid") != null) { + MDC.put("logcontextid", command.getContextParam("logid")); + } + Answer answer = null; + _inProgress.incrementAndGet(); + try { + answer = _resource.executeRequest(command); + } finally { + _inProgress.decrementAndGet(); + } + if (answer != null) { + final Response response = new Response(req, answer); + + if (s_logger.isDebugEnabled()) { + s_logger.debug("Watch Sent: " + response.toString()); + } + try { + task.getLink().send(response.toBytes()); + } catch (final ClosedChannelException e) { + s_logger.warn("Unable to send response: " + response.toString()); + } + } + } else { + s_logger.warn("Ignoring an unknown task"); + } + } + + public synchronized void setLastPingResponseTime() { + _lastPingResponseTime = System.currentTimeMillis(); + } + + protected synchronized long getNextSequence() { + return _sequence++; + } + + @Override + public void registerControlListener(final IAgentControlListener listener) { + synchronized (_controlListeners) { + _controlListeners.add(listener); + } + } + + @Override + public void unregisterControlListener(final IAgentControlListener listener) { + synchronized (_controlListeners) { + _controlListeners.remove(listener); + } + } + + @Override + public AgentControlAnswer sendRequest(final AgentControlCommand cmd, final int timeoutInMilliseconds) throws AgentControlChannelException { + final Request request = new Request(getId(), -1, new Command[]{cmd}, true, false); + request.setSequence(getNextSequence()); + + final AgentControlListener listener = new AgentControlListener(request); + + registerControlListener(listener); + try { + postRequest(request); + synchronized (listener) { + try { + listener.wait(timeoutInMilliseconds); + } catch (final InterruptedException e) { + s_logger.warn("sendRequest is interrupted, exit waiting"); + } + } + + return listener.getAnswer(); + } finally { + unregisterControlListener(listener); + } + } + + @Override + public void postRequest(final AgentControlCommand cmd) throws AgentControlChannelException { + final Request request = new Request(getId(), -1, new Command[]{cmd}, true, false); + request.setSequence(getNextSequence()); + postRequest(request); + } + + private void postRequest(final Request request) throws AgentControlChannelException { + if (_link != null) { + try { + _link.send(request.toBytes()); + } catch (final ClosedChannelException e) { + s_logger.warn("Unable to post agent control reques: " + request.toString()); + throw new AgentControlChannelException("Unable to post agent control request due to " + e.getMessage()); + } + } else { + throw new AgentControlChannelException("Unable to post agent control request as link is not available"); + } + } + + public class AgentControlListener implements IAgentControlListener { + private AgentControlAnswer _answer; + private final Request _request; + + public AgentControlListener(final Request request) { + _request = request; + } + + public AgentControlAnswer getAnswer() { + return _answer; + } + + @Override + public Answer processControlRequest(final Request request, final AgentControlCommand cmd) { + return null; + } + + @Override + public void processControlResponse(final Response response, final AgentControlAnswer answer) { + if (_request.getSequence() == response.getSequence()) { + _answer = answer; + synchronized (this) { + notifyAll(); + } + } + } + } + + protected class ShutdownThread extends Thread { + Agent _agent; + + public ShutdownThread(final Agent agent) { + super("AgentShutdownThread"); + _agent = agent; + } + + @Override + public void run() { + _agent.stop(ShutdownCommand.Requested, null); + } + } + + public class WatchTask extends ManagedContextTimerTask { + protected Request _request; + protected Agent _agent; + protected Link _link; + + public WatchTask(final Link link, final Request request, final Agent agent) { + super(); + _request = request; + _link = link; + _agent = agent; + } + + @Override + protected void runInContext() { + if (s_logger.isTraceEnabled()) { + s_logger.trace("Scheduling " + (_request instanceof Response ? "Ping" : "Watch Task")); + } + try { + if (_request instanceof Response) { + _ugentTaskPool.submit(new ServerHandler(Task.Type.OTHER, _link, _request)); + } else { + _link.schedule(new ServerHandler(Task.Type.OTHER, _link, _request)); + } + } catch (final ClosedChannelException e) { + s_logger.warn("Unable to schedule task because channel is closed"); + } + } + } + + public class StartupTask extends ManagedContextTimerTask { + protected Link _link; + protected volatile boolean cancelled = false; + + public StartupTask(final Link link) { + s_logger.debug("Startup task created"); + _link = link; + } + + @Override + public synchronized boolean cancel() { + // TimerTask.cancel may fail depends on the calling context + if (!cancelled) { + cancelled = true; + _startupWait = _startupWaitDefault; + s_logger.debug("Startup task cancelled"); + return super.cancel(); + } + return true; + } + + @Override + protected synchronized void runInContext() { + if (!cancelled) { + if (s_logger.isInfoEnabled()) { + s_logger.info("The startup command is now cancelled"); + } + cancelled = true; + _startup = null; + _startupWait = _startupWaitDefault * 2; + reconnect(_link); + } + } + } + + public class AgentRequestHandler extends Task { + public AgentRequestHandler(final Task.Type type, final Link link, final Request req) { + super(type, link, req); + } + + @Override + protected void doTask(final Task task) throws TaskExecutionException { + final Request req = (Request) get(); + if (!(req instanceof Response)) { + processRequest(req, task.getLink()); + } + } + } + + public class ServerHandler extends Task { + public ServerHandler(final Task.Type type, final Link link, final byte[] data) { + super(type, link, data); + } + + public ServerHandler(final Task.Type type, final Link link, final Request req) { + super(type, link, req); + } + + @Override + public void doTask(final Task task) throws TaskExecutionException { + if (task.getType() == Task.Type.CONNECT) { + _shell.getBackoffAlgorithm().reset(); + setLink(task.getLink()); + sendStartup(task.getLink()); + } else if (task.getType() == Task.Type.DATA) { + final Request request; + try { + request = Request.parse(task.getData()); + if (request instanceof Response) { + //It's for pinganswer etc, should be processed immediately. + processResponse((Response) request, task.getLink()); + } else { + //put the requests from mgt server into another thread pool, as the request may take a longer time to finish. Don't block the NIO main thread pool + //processRequest(request, task.getLink()); + _executor.submit(new AgentRequestHandler(getType(), getLink(), request)); + } + } catch (final ClassNotFoundException e) { + s_logger.error("Unable to find this request "); + } catch (final Exception e) { + s_logger.error("Error parsing task", e); + } + } else if (task.getType() == Task.Type.DISCONNECT) { + reconnect(task.getLink()); + return; + } else if (task.getType() == Task.Type.OTHER) { + processOtherTask(task); + } + } + } +} diff --git a/cosmic-agent/src/main/java/com/cloud/agent/AgentShell.java b/cosmic-agent/src/main/java/com/cloud/agent/AgentShell.java new file mode 100644 index 0000000000..80e20fdbf0 --- /dev/null +++ b/cosmic-agent/src/main/java/com/cloud/agent/AgentShell.java @@ -0,0 +1,495 @@ +// 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.agent; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.UUID; + +import javax.naming.ConfigurationException; + +import com.cloud.agent.Agent.ExitStatus; +import com.cloud.agent.dao.StorageComponent; +import com.cloud.agent.dao.impl.PropertiesStorage; +import com.cloud.resource.ServerResource; +import com.cloud.utils.LogUtils; +import com.cloud.utils.NumbersUtil; +import com.cloud.utils.ProcessUtil; +import com.cloud.utils.PropertiesUtil; +import com.cloud.utils.backoff.BackoffAlgorithm; +import com.cloud.utils.backoff.impl.ConstantTimeBackoff; +import com.cloud.utils.exception.CloudRuntimeException; + +import org.apache.commons.daemon.Daemon; +import org.apache.commons.daemon.DaemonContext; +import org.apache.commons.daemon.DaemonInitException; +import org.apache.commons.lang.math.NumberUtils; +import org.apache.log4j.xml.DOMConfigurator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AgentShell implements IAgentShell, Daemon { + private static final Logger s_logger = LoggerFactory.getLogger(AgentShell.class.getName()); + + private final Properties _properties = new Properties(); + private final Map _cmdLineProperties = new HashMap(); + private StorageComponent _storage; + private BackoffAlgorithm _backoff; + private String _version; + private String _zone; + private String _pod; + private String _host; + private String _privateIp; + private int _port; + private int _proxyPort; + private int _workers; + private String _guid; + private int _nextAgentId = 1; + private volatile boolean _exit = false; + private int _pingRetries; + private final List _agents = new ArrayList(); + + public AgentShell() { + } + + @Override + public Properties getProperties() { + return _properties; + } + + @Override + public BackoffAlgorithm getBackoffAlgorithm() { + return _backoff; + } + + @Override + public int getPingRetries() { + return _pingRetries; + } + + @Override + public String getVersion() { + return _version; + } + + @Override + public String getZone() { + return _zone; + } + + @Override + public String getPod() { + return _pod; + } + + @Override + public String getHost() { + return _host; + } + + @Override + public String getPrivateIp() { + return _privateIp; + } + + @Override + public int getPort() { + return _port; + } + + @Override + public int getProxyPort() { + return _proxyPort; + } + + @Override + public int getWorkers() { + return _workers; + } + + @Override + public String getGuid() { + return _guid; + } + + @Override + public Map getCmdLineProperties() { + return _cmdLineProperties; + } + + public String getProperty(String prefix, String name) { + if (prefix != null) + return _properties.getProperty(prefix + "." + name); + + return _properties.getProperty(name); + } + + @Override + public String getPersistentProperty(String prefix, String name) { + if (prefix != null) + return _storage.get(prefix + "." + name); + return _storage.get(name); + } + + @Override + public void setPersistentProperty(String prefix, String name, String value) { + // Don't let the agent edit the configuration file. + } + + void loadProperties() throws ConfigurationException { + final File file = PropertiesUtil.findConfigFile("agent.properties"); + + if (null == file) { + throw new ConfigurationException("Unable to find agent.properties."); + } + + s_logger.info("agent.properties found at " + file.getAbsolutePath()); + + try { + PropertiesUtil.loadFromFile(_properties, file); + } catch (final FileNotFoundException ex) { + throw new CloudRuntimeException("Cannot find the file: " + file.getAbsolutePath(), ex); + } catch (final IOException ex) { + throw new CloudRuntimeException("IOException in reading " + file.getAbsolutePath(), ex); + } + } + + protected boolean parseCommand(final String[] args) throws ConfigurationException { + String host = null; + String workers = null; + String port = null; + String zone = null; + String pod = null; + String guid = null; + for (String param : args) { + final String[] tokens = param.split("="); + if (tokens.length != 2) { + System.out.println("Invalid Parameter: " + param); + continue; + } + final String paramName = tokens[0]; + final String paramValue = tokens[1]; + + // save command line properties + _cmdLineProperties.put(paramName, paramValue); + + if (paramName.equalsIgnoreCase("port")) { + port = paramValue; + } else if (paramName.equalsIgnoreCase("threads") || paramName.equalsIgnoreCase("workers")) { + workers = paramValue; + } else if (paramName.equalsIgnoreCase("host")) { + host = paramValue; + } else if (paramName.equalsIgnoreCase("zone")) { + zone = paramValue; + } else if (paramName.equalsIgnoreCase("pod")) { + pod = paramValue; + } else if (paramName.equalsIgnoreCase("guid")) { + guid = paramValue; + } else if (paramName.equalsIgnoreCase("eth1ip")) { + _privateIp = paramValue; + } + } + + if (port == null) { + port = getProperty(null, "port"); + } + + _port = NumberUtils.toInt(port, 8250); + + _proxyPort = NumberUtils.toInt(getProperty(null, "consoleproxy.httpListenPort"), 443); + + if (workers == null) { + workers = getProperty(null, "workers"); + } + + _workers = NumberUtils.toInt(workers, 5); + if (_workers <= 0) { + _workers = 5; + } + + if (host == null) { + host = getProperty(null, "host"); + } + + if (host == null) { + host = "localhost"; + } + _host = host; + + if (zone != null) + _zone = zone; + else + _zone = getProperty(null, "zone"); + if (_zone == null || (_zone.startsWith("@") && _zone.endsWith("@"))) { + _zone = "default"; + } + + if (pod != null) + _pod = pod; + else + _pod = getProperty(null, "pod"); + if (_pod == null || (_pod.startsWith("@") && _pod.endsWith("@"))) { + _pod = "default"; + } + + if (_host == null || (_host.startsWith("@") && _host.endsWith("@"))) { + throw new ConfigurationException("Host is not configured correctly: " + _host); + } + + final String retries = getProperty(null, "ping.retries"); + _pingRetries = NumbersUtil.parseInt(retries, 5); + + String value = getProperty(null, "developer"); + boolean developer = Boolean.parseBoolean(value); + + if (guid != null) + _guid = guid; + else + _guid = getProperty(null, "guid"); + if (_guid == null) { + if (!developer) { + throw new ConfigurationException("Unable to find the guid"); + } + _guid = UUID.randomUUID().toString(); + _properties.setProperty("guid", _guid); + } + + return true; + } + + @Override + public void init(DaemonContext dc) throws DaemonInitException { + s_logger.debug("Initializing AgentShell from JSVC"); + try { + init(dc.getArguments()); + } catch (ConfigurationException ex) { + throw new DaemonInitException("Initialization failed", ex); + } + } + + public void init(String[] args) throws ConfigurationException { + + // PropertiesUtil is used both in management server and agent packages, + // it searches path under class path and common J2EE containers + // For KVM agent, do it specially here + + File file = new File("/etc/cosmic/agent/log4j-cloud.xml"); + if (!file.exists()) { + file = PropertiesUtil.findConfigFile("log4j-cloud.xml"); + } + + if (null != file) { + DOMConfigurator.configureAndWatch(file.getAbsolutePath()); + + s_logger.info("Agent started"); + } else { + s_logger.error("Could not start the Agent because the absolut path of the \"log4j-cloud.xml\" file cannot be determined."); + } + + final Class c = this.getClass(); + _version = c.getPackage().getImplementationVersion(); + if (_version == null) { + throw new CloudRuntimeException("Unable to find the implementation version of this agent"); + } + s_logger.info("Implementation Version is " + _version); + + loadProperties(); + parseCommand(args); + + if (s_logger.isDebugEnabled()) { + List properties = Collections.list((Enumeration)_properties.propertyNames()); + for (String property : properties) { + s_logger.debug("Found property: " + property); + } + } + + s_logger.info("Defaulting to using properties file for storage"); + _storage = new PropertiesStorage(); + _storage.configure("Storage", new HashMap()); + + // merge with properties from command line to let resource access + // command line parameters + for (Map.Entry cmdLineProp : getCmdLineProperties().entrySet()) { + _properties.put(cmdLineProp.getKey(), cmdLineProp.getValue()); + } + + s_logger.info("Defaulting to the constant time backoff algorithm"); + _backoff = new ConstantTimeBackoff(); + _backoff.configure("ConstantTimeBackoff", new HashMap()); + } + + private void launchAgent() throws ConfigurationException { + String resourceClassNames = getProperty(null, "resource"); + s_logger.trace("resource=" + resourceClassNames); + if (resourceClassNames != null) { + launchAgentFromClassInfo(resourceClassNames); + return; + } + + launchAgentFromTypeInfo(); + } + + private void launchAgentFromClassInfo(String resourceClassNames) throws ConfigurationException { + String[] names = resourceClassNames.split("\\|"); + for (String name : names) { + Class impl; + try { + impl = Class.forName(name); + final Constructor constructor = impl.getDeclaredConstructor(); + constructor.setAccessible(true); + ServerResource resource = (ServerResource)constructor.newInstance(); + launchAgent(getNextAgentId(), resource); + } catch (final ClassNotFoundException e) { + throw new ConfigurationException("Resource class not found: " + name + " due to: " + e.toString()); + } catch (final SecurityException e) { + throw new ConfigurationException("Security excetion when loading resource: " + name + " due to: " + e.toString()); + } catch (final NoSuchMethodException e) { + throw new ConfigurationException("Method not found excetion when loading resource: " + name + " due to: " + e.toString()); + } catch (final IllegalArgumentException e) { + throw new ConfigurationException("Illegal argument excetion when loading resource: " + name + " due to: " + e.toString()); + } catch (final InstantiationException e) { + throw new ConfigurationException("Instantiation excetion when loading resource: " + name + " due to: " + e.toString()); + } catch (final IllegalAccessException e) { + throw new ConfigurationException("Illegal access exception when loading resource: " + name + " due to: " + e.toString()); + } catch (final InvocationTargetException e) { + throw new ConfigurationException("Invocation target exception when loading resource: " + name + " due to: " + e.toString()); + } + } + } + + private void launchAgentFromTypeInfo() throws ConfigurationException { + String typeInfo = getProperty(null, "type"); + if (typeInfo == null) { + s_logger.error("Unable to retrieve the type"); + throw new ConfigurationException("Unable to retrieve the type of this agent."); + } + s_logger.trace("Launching agent based on type=" + typeInfo); + } + + private void launchAgent(int localAgentId, ServerResource resource) throws ConfigurationException { + // we don't track agent after it is launched for now + Agent agent = new Agent(this, localAgentId, resource); + _agents.add(agent); + agent.start(); + } + + public synchronized int getNextAgentId() { + return _nextAgentId++; + } + + @Override + public void start() { + try { + /* By default we only search for log4j.xml */ + LogUtils.initLog4j("log4j-cloud.xml"); + + boolean ipv6disabled = false; + String ipv6 = getProperty(null, "ipv6disabled"); + if (ipv6 != null) { + ipv6disabled = Boolean.parseBoolean(ipv6); + } + + boolean ipv6prefer = false; + String ipv6p = getProperty(null, "ipv6prefer"); + if (ipv6p != null) { + ipv6prefer = Boolean.parseBoolean(ipv6p); + } + + if (ipv6disabled) { + s_logger.info("Preferring IPv4 address family for agent connection"); + System.setProperty("java.net.preferIPv4Stack", "true"); + if (ipv6prefer) { + s_logger.info("ipv6prefer is set to true, but ipv6disabled is false. Not preferring IPv6 for agent connection"); + } + } else { + if (ipv6prefer) { + s_logger.info("Preferring IPv6 address family for agent connection"); + System.setProperty("java.net.preferIPv6Addresses", "true"); + } else { + s_logger.info("Using default Java settings for IPv6 preference for agent connection"); + } + } + + String instance = getProperty(null, "instance"); + if (instance == null) { + if (Boolean.parseBoolean(getProperty(null, "developer"))) { + instance = UUID.randomUUID().toString(); + } else { + instance = ""; + } + } else { + instance += "."; + } + + String pidDir = getProperty(null, "piddir"); + + final String run = "agent." + instance + "pid"; + s_logger.debug("Checking to see if " + run + " exists."); + ProcessUtil.pidCheck(pidDir, run); + + launchAgent(); + + try { + while (!_exit) + Thread.sleep(1000); + } catch (InterruptedException e) { + s_logger.debug("[ignored] AgentShell was interupted."); + } + + } catch (final ConfigurationException e) { + s_logger.error("Unable to start agent: " + e.getMessage()); + System.out.println("Unable to start agent: " + e.getMessage()); + System.exit(ExitStatus.Configuration.value()); + } catch (final Exception e) { + s_logger.error("Unable to start agent: ", e); + System.out.println("Unable to start agent: " + e.getMessage()); + System.exit(ExitStatus.Error.value()); + } + } + + @Override + public void stop() { + _exit = true; + } + + @Override + public void destroy() { + + } + + public static void main(String[] args) { + try { + s_logger.debug("Initializing AgentShell from main"); + AgentShell shell = new AgentShell(); + shell.init(args); + shell.start(); + } catch (ConfigurationException e) { + System.out.println(e.getMessage()); + } + } + +} diff --git a/cosmic-agent/src/main/java/com/cloud/agent/IAgentShell.java b/cosmic-agent/src/main/java/com/cloud/agent/IAgentShell.java new file mode 100644 index 0000000000..dde67381a4 --- /dev/null +++ b/cosmic-agent/src/main/java/com/cloud/agent/IAgentShell.java @@ -0,0 +1,54 @@ +// 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.agent; + +import java.util.Map; +import java.util.Properties; + +import com.cloud.utils.backoff.BackoffAlgorithm; + +public interface IAgentShell { + public Map getCmdLineProperties(); + + public Properties getProperties(); + + public String getPersistentProperty(String prefix, String name); + + public void setPersistentProperty(String prefix, String name, String value); + + public String getHost(); + + public String getPrivateIp(); + + public int getPort(); + + public int getWorkers(); + + public int getProxyPort(); + + public String getGuid(); + + public String getZone(); + + public String getPod(); + + public BackoffAlgorithm getBackoffAlgorithm(); + + public int getPingRetries(); + + public String getVersion(); +} diff --git a/cosmic-agent/src/main/java/com/cloud/agent/dao/StorageComponent.java b/cosmic-agent/src/main/java/com/cloud/agent/dao/StorageComponent.java new file mode 100644 index 0000000000..e98007322b --- /dev/null +++ b/cosmic-agent/src/main/java/com/cloud/agent/dao/StorageComponent.java @@ -0,0 +1,28 @@ +// 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.agent.dao; + +import com.cloud.utils.component.Manager; + +/** + * + */ +public interface StorageComponent extends Manager { + String get(String key); + + void persist(String key, String value); +} diff --git a/cosmic-agent/src/main/java/com/cloud/agent/dao/impl/PropertiesStorage.java b/cosmic-agent/src/main/java/com/cloud/agent/dao/impl/PropertiesStorage.java new file mode 100644 index 0000000000..62e4846085 --- /dev/null +++ b/cosmic-agent/src/main/java/com/cloud/agent/dao/impl/PropertiesStorage.java @@ -0,0 +1,147 @@ +// 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.agent.dao.impl; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Map; +import java.util.Properties; + +import javax.ejb.Local; + +import com.cloud.agent.dao.StorageComponent; +import com.cloud.utils.PropertiesUtil; + +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Uses Properties to implement storage. + * + * @config {@table || Param Name | Description | Values | Default || || path | + * path to the properties _file | String | db/db.properties || * } + **/ +@Local(value = {StorageComponent.class}) +public class PropertiesStorage implements StorageComponent { + private static final Logger s_logger = LoggerFactory.getLogger(PropertiesStorage.class); + Properties _properties = new Properties(); + File _file; + String _name; + + @Override + public synchronized String get(String key) { + return _properties.getProperty(key); + } + + @Override + public synchronized void persist(String key, String value) { + _properties.setProperty(key, value); + FileOutputStream output = null; + try { + output = new FileOutputStream(_file); + _properties.store(output, _name); + output.flush(); + output.close(); + } catch (IOException e) { + s_logger.error("Uh-oh: ", e); + } finally { + IOUtils.closeQuietly(output); + } + } + + @Override + public synchronized boolean configure(String name, Map params) { + _name = name; + String path = (String)params.get("path"); + if (path == null) { + path = "agent.properties"; + } + + File file = PropertiesUtil.findConfigFile(path); + if (file == null) { + file = new File(path); + try { + if (!file.createNewFile()) { + s_logger.error("Unable to create _file: " + file.getAbsolutePath()); + return false; + } + } catch (IOException e) { + s_logger.error("Unable to create _file: " + file.getAbsolutePath(), e); + return false; + } + } + try { + PropertiesUtil.loadFromFile(_properties, file); + _file = file; + } catch (FileNotFoundException e) { + s_logger.error("How did we get here? ", e); + return false; + } catch (IOException e) { + s_logger.error("IOException: ", e); + return false; + } + return true; + } + + @Override + public synchronized String getName() { + return _name; + } + + @Override + public boolean start() { + return true; + } + + @Override + public boolean stop() { + return true; + } + + @Override + public void setName(String name) { + // TODO Auto-generated method stub + + } + + @Override + public void setConfigParams(Map params) { + // TODO Auto-generated method stub + + } + + @Override + public Map getConfigParams() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getRunLevel() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void setRunLevel(int level) { + // TODO Auto-generated method stub + } + +} diff --git a/cosmic-agent/src/main/java/com/cloud/agent/resource/consoleproxy/ConsoleProxyAuthenticationResult.java b/cosmic-agent/src/main/java/com/cloud/agent/resource/consoleproxy/ConsoleProxyAuthenticationResult.java new file mode 100644 index 0000000000..130b104b21 --- /dev/null +++ b/cosmic-agent/src/main/java/com/cloud/agent/resource/consoleproxy/ConsoleProxyAuthenticationResult.java @@ -0,0 +1,80 @@ +// 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.agent.resource.consoleproxy; + +public class ConsoleProxyAuthenticationResult { + private boolean success; + private boolean isReauthentication; + private String host; + private int port; + private String tunnelUrl; + private String tunnelSession; + + public ConsoleProxyAuthenticationResult() { + success = false; + isReauthentication = false; + port = 0; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean isReauthentication() { + return isReauthentication; + } + + public void setReauthentication(boolean isReauthentication) { + this.isReauthentication = isReauthentication; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public String getTunnelUrl() { + return tunnelUrl; + } + + public void setTunnelUrl(String tunnelUrl) { + this.tunnelUrl = tunnelUrl; + } + + public String getTunnelSession() { + return tunnelSession; + } + + public void setTunnelSession(String tunnelSession) { + this.tunnelSession = tunnelSession; + } +} diff --git a/cosmic-agent/src/main/java/com/cloud/agent/resource/consoleproxy/ConsoleProxyResource.java b/cosmic-agent/src/main/java/com/cloud/agent/resource/consoleproxy/ConsoleProxyResource.java new file mode 100644 index 0000000000..8f739cf83b --- /dev/null +++ b/cosmic-agent/src/main/java/com/cloud/agent/resource/consoleproxy/ConsoleProxyResource.java @@ -0,0 +1,463 @@ +// 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.agent.resource.consoleproxy; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLConnection; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import javax.naming.ConfigurationException; + +import com.cloud.agent.Agent.ExitStatus; +import com.cloud.agent.api.AgentControlAnswer; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.CheckHealthAnswer; +import com.cloud.agent.api.CheckHealthCommand; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.ConsoleAccessAuthenticationAnswer; +import com.cloud.agent.api.ConsoleAccessAuthenticationCommand; +import com.cloud.agent.api.ConsoleProxyLoadReportCommand; +import com.cloud.agent.api.PingCommand; +import com.cloud.agent.api.ReadyAnswer; +import com.cloud.agent.api.ReadyCommand; +import com.cloud.agent.api.StartupCommand; +import com.cloud.agent.api.StartupProxyCommand; +import com.cloud.agent.api.proxy.CheckConsoleProxyLoadCommand; +import com.cloud.agent.api.proxy.ConsoleProxyLoadAnswer; +import com.cloud.agent.api.proxy.StartConsoleProxyAgentHttpHandlerCommand; +import com.cloud.agent.api.proxy.WatchConsoleProxyLoadCommand; +import com.cloud.exception.AgentControlChannelException; +import com.cloud.host.Host; +import com.cloud.host.Host.Type; +import com.cloud.resource.ServerResource; +import com.cloud.resource.ServerResourceBase; +import com.cloud.utils.NumbersUtil; +import com.cloud.utils.net.NetUtils; +import com.cloud.utils.script.Script; +import com.google.gson.Gson; + +import org.apache.cloudstack.managed.context.ManagedContextRunnable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * I don't want to introduce extra cross-cutting concerns into console proxy + * process, as it involves configurations like zone/pod, agent auto self-upgrade + * etc. I also don't want to introduce more module dependency issues into our + * build system, cross-communication between this resource and console proxy + * will be done through reflection. As a result, come out with following + * solution to solve the problem of building a communication channel between + * consoole proxy and management server. + * + * We will deploy an agent shell inside console proxy VM, and this agent shell + * will launch current console proxy from within this special server resource, + * through it console proxy can build a communication channel with management + * server. + * + */ +public class ConsoleProxyResource extends ServerResourceBase implements ServerResource { + static final Logger s_logger = LoggerFactory.getLogger(ConsoleProxyResource.class); + + private final Properties _properties = new Properties(); + private Thread _consoleProxyMain = null; + + long _proxyVmId; + int _proxyPort; + + String _localgw; + String _eth1ip; + String _eth1mask; + String _pubIp; + + @Override + public Answer executeRequest(final Command cmd) { + if (cmd instanceof CheckConsoleProxyLoadCommand) { + return execute((CheckConsoleProxyLoadCommand)cmd); + } else if (cmd instanceof WatchConsoleProxyLoadCommand) { + return execute((WatchConsoleProxyLoadCommand)cmd); + } else if (cmd instanceof ReadyCommand) { + s_logger.info("Receive ReadyCommand, response with ReadyAnswer"); + return new ReadyAnswer((ReadyCommand)cmd); + } else if (cmd instanceof CheckHealthCommand) { + return new CheckHealthAnswer((CheckHealthCommand)cmd, true); + } else if (cmd instanceof StartConsoleProxyAgentHttpHandlerCommand) { + return execute((StartConsoleProxyAgentHttpHandlerCommand)cmd); + } else { + return Answer.createUnsupportedCommandAnswer(cmd); + } + } + + private Answer execute(StartConsoleProxyAgentHttpHandlerCommand cmd) { + s_logger.info("Invoke launchConsoleProxy() in responding to StartConsoleProxyAgentHttpHandlerCommand"); + launchConsoleProxy(cmd.getKeystoreBits(), cmd.getKeystorePassword(), cmd.getEncryptorPassword()); + return new Answer(cmd); + } + + private void disableRpFilter() { + try (FileWriter fstream = new FileWriter("/proc/sys/net/ipv4/conf/eth2/rp_filter"); + BufferedWriter out = new BufferedWriter(fstream);) + { + out.write("0"); + } catch (IOException e) { + s_logger.warn("Unable to disable rp_filter"); + } + } + + protected Answer execute(final CheckConsoleProxyLoadCommand cmd) { + return executeProxyLoadScan(cmd, cmd.getProxyVmId(), cmd.getProxyVmName(), cmd.getProxyManagementIp(), cmd.getProxyCmdPort()); + } + + protected Answer execute(final WatchConsoleProxyLoadCommand cmd) { + return executeProxyLoadScan(cmd, cmd.getProxyVmId(), cmd.getProxyVmName(), cmd.getProxyManagementIp(), cmd.getProxyCmdPort()); + } + + private Answer executeProxyLoadScan(final Command cmd, final long proxyVmId, final String proxyVmName, final String proxyManagementIp, final int cmdPort) { + String result = null; + + final StringBuffer sb = new StringBuffer(); + sb.append("http://").append(proxyManagementIp).append(":" + cmdPort).append("/cmd/getstatus"); + + boolean success = true; + try { + final URL url = new URL(sb.toString()); + final URLConnection conn = url.openConnection(); + + final InputStream is = conn.getInputStream(); + final BufferedReader reader = new BufferedReader(new InputStreamReader(is,"UTF-8")); + final StringBuilder sb2 = new StringBuilder(); + String line = null; + try { + while ((line = reader.readLine()) != null) + sb2.append(line + "\n"); + result = sb2.toString(); + } catch (final IOException e) { + success = false; + } finally { + try { + is.close(); + } catch (final IOException e) { + s_logger.warn("Exception when closing , console proxy address : " + proxyManagementIp); + success = false; + } + } + } catch (final IOException e) { + s_logger.warn("Unable to open console proxy command port url, console proxy address : " + proxyManagementIp); + success = false; + } + + return new ConsoleProxyLoadAnswer(cmd, proxyVmId, proxyVmName, success, result); + } + + @Override + protected String getDefaultScriptsDir() { + return null; + } + + @Override + public Type getType() { + return Host.Type.ConsoleProxy; + } + + @Override + public synchronized StartupCommand[] initialize() { + final StartupProxyCommand cmd = new StartupProxyCommand(); + fillNetworkInformation(cmd); + cmd.setProxyPort(_proxyPort); + cmd.setProxyVmId(_proxyVmId); + if (_pubIp != null) + cmd.setPublicIpAddress(_pubIp); + return new StartupCommand[] {cmd}; + } + + @Override + public void disconnected() { + } + + @Override + public PingCommand getCurrentStatus(long id) { + return new PingCommand(Type.ConsoleProxy, id); + } + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + _localgw = (String)params.get("localgw"); + _eth1mask = (String)params.get("eth1mask"); + _eth1ip = (String)params.get("eth1ip"); + if (_eth1ip != null) { + params.put("private.network.device", "eth1"); + } else { + s_logger.info("eth1ip parameter has not been configured, assuming that we are not inside a system vm"); + } + + String eth2ip = (String)params.get("eth2ip"); + if (eth2ip != null) { + params.put("public.network.device", "eth2"); + } else { + s_logger.info("eth2ip parameter is not found, assuming that we are not inside a system vm"); + } + + super.configure(name, params); + + for (Map.Entry entry : params.entrySet()) { + _properties.put(entry.getKey(), entry.getValue()); + } + + String value = (String)params.get("premium"); + if (value != null && value.equals("premium")) + _proxyPort = 443; + else { + value = (String)params.get("consoleproxy.httpListenPort"); + _proxyPort = NumbersUtil.parseInt(value, 80); + } + + value = (String)params.get("proxy_vm"); + _proxyVmId = NumbersUtil.parseLong(value, 0); + + if (_localgw != null) { + String mgmtHost = (String)params.get("host"); + if (_eth1ip != null) { + addRouteToInternalIpOrCidr(_localgw, _eth1ip, _eth1mask, mgmtHost); + String internalDns1 = (String) params.get("internaldns1"); + if (internalDns1 == null) { + s_logger.warn("No DNS entry found during configuration of NfsSecondaryStorage"); + } else { + addRouteToInternalIpOrCidr(_localgw, _eth1ip, _eth1mask, internalDns1); + } + String internalDns2 = (String) params.get("internaldns2"); + if (internalDns2 != null) { + addRouteToInternalIpOrCidr(_localgw, _eth1ip, _eth1mask, internalDns2); + } + } + } + + _pubIp = (String)params.get("public.ip"); + + value = (String)params.get("disable_rp_filter"); + if (value != null && value.equalsIgnoreCase("true")) { + disableRpFilter(); + } + + if (s_logger.isInfoEnabled()) + s_logger.info("Receive proxyVmId in ConsoleProxyResource configuration as " + _proxyVmId); + + return true; + } + + private void addRouteToInternalIpOrCidr(String localgw, String eth1ip, String eth1mask, String destIpOrCidr) { + s_logger.debug("addRouteToInternalIp: localgw=" + localgw + ", eth1ip=" + eth1ip + ", eth1mask=" + eth1mask + ",destIp=" + destIpOrCidr); + if (destIpOrCidr == null) { + s_logger.debug("addRouteToInternalIp: destIp is null"); + return; + } + if (!NetUtils.isValidIp(destIpOrCidr) && !NetUtils.isValidCIDR(destIpOrCidr)) { + s_logger.warn(" destIp is not a valid ip address or cidr destIp=" + destIpOrCidr); + return; + } + boolean inSameSubnet = false; + if (NetUtils.isValidIp(destIpOrCidr)) { + if (eth1ip != null && eth1mask != null) { + inSameSubnet = NetUtils.sameSubnet(eth1ip, destIpOrCidr, eth1mask); + } else { + s_logger.warn("addRouteToInternalIp: unable to determine same subnet: _eth1ip=" + eth1ip + ", dest ip=" + destIpOrCidr + ", _eth1mask=" + eth1mask); + } + } else { + inSameSubnet = NetUtils.isNetworkAWithinNetworkB(destIpOrCidr, NetUtils.ipAndNetMaskToCidr(eth1ip, eth1mask)); + } + if (inSameSubnet) { + s_logger.debug("addRouteToInternalIp: dest ip " + destIpOrCidr + " is in the same subnet as eth1 ip " + eth1ip); + return; + } + Script command = new Script("/bin/bash", s_logger); + command.add("-c"); + command.add("ip route delete " + destIpOrCidr); + command.execute(); + command = new Script("/bin/bash", s_logger); + command.add("-c"); + command.add("ip route add " + destIpOrCidr + " via " + localgw); + String result = command.execute(); + if (result != null) { + s_logger.warn("Error in configuring route to internal ip err=" + result); + } else { + s_logger.debug("addRouteToInternalIp: added route to internal ip=" + destIpOrCidr + " via " + localgw); + } + } + + @Override + public String getName() { + return _name; + } + + private void launchConsoleProxy(final byte[] ksBits, final String ksPassword, final String encryptorPassword) { + final Object resource = this; + if (_consoleProxyMain == null) { + _consoleProxyMain = new Thread(new ManagedContextRunnable() { + @Override + protected void runInContext() { + try { + Class consoleProxyClazz = Class.forName("com.cloud.consoleproxy.ConsoleProxy"); + try { + s_logger.info("Invoke setEncryptorPassword(), ecnryptorPassword: " + encryptorPassword); + Method methodSetup = consoleProxyClazz.getMethod("setEncryptorPassword", String.class); + methodSetup.invoke(null, encryptorPassword); + + s_logger.info("Invoke startWithContext()"); + Method method = consoleProxyClazz.getMethod("startWithContext", Properties.class, Object.class, byte[].class, String.class); + method.invoke(null, _properties, resource, ksBits, ksPassword); + } catch (SecurityException e) { + s_logger.error("Unable to launch console proxy due to SecurityException", e); + System.exit(ExitStatus.Error.value()); + } catch (NoSuchMethodException e) { + s_logger.error("Unable to launch console proxy due to NoSuchMethodException", e); + System.exit(ExitStatus.Error.value()); + } catch (IllegalArgumentException e) { + s_logger.error("Unable to launch console proxy due to IllegalArgumentException", e); + System.exit(ExitStatus.Error.value()); + } catch (IllegalAccessException e) { + s_logger.error("Unable to launch console proxy due to IllegalAccessException", e); + System.exit(ExitStatus.Error.value()); + } catch (InvocationTargetException e) { + s_logger.error("Unable to launch console proxy due to InvocationTargetException " + e.getTargetException().toString(), e); + System.exit(ExitStatus.Error.value()); + } + } catch (final ClassNotFoundException e) { + s_logger.error("Unable to launch console proxy due to ClassNotFoundException"); + System.exit(ExitStatus.Error.value()); + } + } + }, "Console-Proxy-Main"); + _consoleProxyMain.setDaemon(true); + _consoleProxyMain.start(); + } else { + s_logger.info("com.cloud.consoleproxy.ConsoleProxy is already running"); + + try { + Class consoleProxyClazz = Class.forName("com.cloud.consoleproxy.ConsoleProxy"); + Method methodSetup = consoleProxyClazz.getMethod("setEncryptorPassword", String.class); + methodSetup.invoke(null, encryptorPassword); + } catch (SecurityException e) { + s_logger.error("Unable to launch console proxy due to SecurityException", e); + System.exit(ExitStatus.Error.value()); + } catch (NoSuchMethodException e) { + s_logger.error("Unable to launch console proxy due to NoSuchMethodException", e); + System.exit(ExitStatus.Error.value()); + } catch (IllegalArgumentException e) { + s_logger.error("Unable to launch console proxy due to IllegalArgumentException", e); + System.exit(ExitStatus.Error.value()); + } catch (IllegalAccessException e) { + s_logger.error("Unable to launch console proxy due to IllegalAccessException", e); + System.exit(ExitStatus.Error.value()); + } catch (InvocationTargetException e) { + s_logger.error("Unable to launch console proxy due to InvocationTargetException " + e.getTargetException().toString(), e); + System.exit(ExitStatus.Error.value()); + } catch (final ClassNotFoundException e) { + s_logger.error("Unable to launch console proxy due to ClassNotFoundException", e); + System.exit(ExitStatus.Error.value()); + } + } + } + + public String authenticateConsoleAccess(String host, String port, String vmId, String sid, String ticket, Boolean isReauthentication) { + + ConsoleAccessAuthenticationCommand cmd = new ConsoleAccessAuthenticationCommand(host, port, vmId, sid, ticket); + cmd.setReauthenticating(isReauthentication); + + ConsoleProxyAuthenticationResult result = new ConsoleProxyAuthenticationResult(); + result.setSuccess(false); + result.setReauthentication(isReauthentication); + + try { + AgentControlAnswer answer = getAgentControl().sendRequest(cmd, 10000); + + if (answer != null) { + ConsoleAccessAuthenticationAnswer authAnswer = (ConsoleAccessAuthenticationAnswer)answer; + result.setSuccess(authAnswer.succeeded()); + result.setHost(authAnswer.getHost()); + result.setPort(authAnswer.getPort()); + result.setTunnelUrl(authAnswer.getTunnelUrl()); + result.setTunnelSession(authAnswer.getTunnelSession()); + } else { + s_logger.error("Authentication failed for vm: " + vmId + " with sid: " + sid); + } + } catch (AgentControlChannelException e) { + s_logger.error("Unable to send out console access authentication request due to " + e.getMessage(), e); + } + + return new Gson().toJson(result); + } + + public void reportLoadInfo(String gsonLoadInfo) { + ConsoleProxyLoadReportCommand cmd = new ConsoleProxyLoadReportCommand(_proxyVmId, gsonLoadInfo); + try { + getAgentControl().postRequest(cmd); + + if (s_logger.isDebugEnabled()) + s_logger.debug("Report proxy load info, proxy : " + _proxyVmId + ", load: " + gsonLoadInfo); + } catch (AgentControlChannelException e) { + s_logger.error("Unable to send out load info due to " + e.getMessage(), e); + } + } + + public void ensureRoute(String address) { + if (_localgw != null) { + if (s_logger.isDebugEnabled()) + s_logger.debug("Ensure route for " + address + " via " + _localgw); + + // this method won't be called in high frequency, serialize access + // to script execution + synchronized (this) { + try { + addRouteToInternalIpOrCidr(_localgw, _eth1ip, _eth1mask, address); + } catch (Throwable e) { + s_logger.warn("Unexpected exception while adding internal route to " + address, e); + } + } + } + } + + @Override + public void setName(String name) { + } + + @Override + public void setConfigParams(Map params) { + } + + @Override + public Map getConfigParams() { + return new HashMap(); + } + + @Override + public int getRunLevel() { + return 0; + } + + @Override + public void setRunLevel(int level) { + } +} diff --git a/cosmic-agent/src/test/java/com/cloud/agent/AgentShellTest.java b/cosmic-agent/src/test/java/com/cloud/agent/AgentShellTest.java new file mode 100644 index 0000000000..5baa7bf800 --- /dev/null +++ b/cosmic-agent/src/test/java/com/cloud/agent/AgentShellTest.java @@ -0,0 +1,47 @@ +// 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.agent; + +import java.util.UUID; + +import javax.naming.ConfigurationException; + +import org.junit.Assert; +import org.junit.Test; + +public class AgentShellTest { + @Test + public void parseCommand() throws ConfigurationException { + AgentShell shell = new AgentShell(); + UUID anyUuid = UUID.randomUUID(); + shell.parseCommand(new String[] {"port=55555", "threads=4", "host=localhost", "pod=pod1", "guid=" + anyUuid, "zone=zone1"}); + Assert.assertEquals(55555, shell.getPort()); + Assert.assertEquals(4, shell.getWorkers()); + Assert.assertEquals("localhost", shell.getHost()); + Assert.assertEquals(anyUuid.toString(), shell.getGuid()); + Assert.assertEquals("pod1", shell.getPod()); + Assert.assertEquals("zone1", shell.getZone()); + } + + @Test + public void loadProperties() throws ConfigurationException { + AgentShell shell = new AgentShell(); + shell.loadProperties(); + Assert.assertNotNull(shell.getProperties()); + Assert.assertFalse(shell.getProperties().entrySet().isEmpty()); + } +} diff --git a/cosmic-agent/src/test/java/com/cloud/agent/dao/impl/PropertiesStorageTest.java b/cosmic-agent/src/test/java/com/cloud/agent/dao/impl/PropertiesStorageTest.java new file mode 100644 index 0000000000..b877a6b5f9 --- /dev/null +++ b/cosmic-agent/src/test/java/com/cloud/agent/dao/impl/PropertiesStorageTest.java @@ -0,0 +1,67 @@ +/* + * 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.agent.dao.impl; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; + +import org.apache.commons.io.FileUtils; +import org.junit.Test; + +import junit.framework.Assert; + +public class PropertiesStorageTest { + @Test + public void configureWithNotExistingFile() { + String fileName = "target/notyetexistingfile" + System.currentTimeMillis(); + File file = new File(fileName); + + PropertiesStorage storage = new PropertiesStorage(); + HashMap params = new HashMap(); + params.put("path", fileName); + Assert.assertTrue(storage.configure("test", params)); + Assert.assertTrue(file.exists()); + storage.persist("foo", "bar"); + Assert.assertEquals("bar", storage.get("foo")); + + storage.stop(); + file.delete(); + } + + @Test + public void configureWithExistingFile() throws IOException { + String fileName = "target/existingfile" + System.currentTimeMillis(); + File file = new File(fileName); + + FileUtils.writeStringToFile(file, "a=b\n\n"); + + PropertiesStorage storage = new PropertiesStorage(); + HashMap params = new HashMap(); + params.put("path", fileName); + Assert.assertTrue(storage.configure("test", params)); + Assert.assertEquals("b", storage.get("a")); + Assert.assertTrue(file.exists()); + storage.persist("foo", "bar"); + Assert.assertEquals("bar", storage.get("foo")); + + storage.stop(); + file.delete(); + } +} diff --git a/cosmic-client/LICENSE b/cosmic-client/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/cosmic-client/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/cosmic-client/README.md b/cosmic-client/README.md new file mode 100644 index 0000000000..fed57c8755 --- /dev/null +++ b/cosmic-client/README.md @@ -0,0 +1 @@ +# cosmic-client \ No newline at end of file diff --git a/cosmic-client/WEB-INF/classes/resources/messages.properties b/cosmic-client/WEB-INF/classes/resources/messages.properties new file mode 100644 index 0000000000..f3b19f002c --- /dev/null +++ b/cosmic-client/WEB-INF/classes/resources/messages.properties @@ -0,0 +1,2198 @@ +# 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. +label.add.ldap.account=Add LDAP account +label.vm.ip=VM IP Address +message.listView.subselect.multi=(Ctrl/Cmd-click) +label.use.vm.ips=Use VM IPs +label.reinstall.vm=Reinstall VM +message.reinstall.vm=NOTE: Proceed with caution. This will cause the VM to be reinstalled from the template; data on the root disk will be lost. Extra data volumes, if any, will not be touched. +label.recover.vm=Recover VM +message.recover.vm=Please confirm that you would like to recover this VM. +label.port=Port +label.remove.ldap=Remove LDAP +label.configure.ldap=Configure LDAP +label.ldap.configuration=LDAP Configuration +label.ldap.port=LDAP port +label.create.nfs.secondary.staging.store=Create NFS secondary staging store +label.volatile=Volatile +label.planner.mode=Planner mode +label.deployment.planner=Deployment planner +label.quiesce.vm=Quiesce VM +label.smb.username=SMB Username +label.smb.password=SMB Password +label.smb.domain=SMB Domain +label.hypervisors=Hypervisors +label.home=Home +label.sockets=CPU Sockets +label.root.disk.size=Root disk size (GB) +label.s3.nfs.server=S3 NFS Server +label.s3.nfs.path=S3 NFS Path +label.delete.events=Delete events +label.delete.alerts=Delete alerts +label.archive.alerts=Archive alerts +label.archive.events=Archive events +label.by.alert.type=By alert type +label.by.event.type=By event type +label.by.date.start=By date (start) +label.by.date.end=By date (end) +label.switch.type=Switch Type +label.service.state=Service State +label.egress.default.policy=Egress Default Policy +label.routing=Routing +label.hvm=HVM +label.about=About +label.app.name=Cosmic +label.about.app=About Cosmic +label.custom.disk.iops=Custom IOPS +label.disk.iops.min=Min IOPS +label.disk.iops.max=Max IOPS +label.disk.iops.total=IOPS Total +label.hypervisor.snapshot.reserve=Hypervisor Snapshot Reserve +label.secondary.ips=Secondary IPs +label.edit.secondary.ips=Edit secondary IPs +label.view.secondary.ips=View secondary IPs +message.validate.invalid.characters=Invalid characters found; please correct. +message.acquire.ip.nic=Please confirm that you would like to acquire a new secondary IP for this NIC.
NOTE: You need to manually configure the newly-acquired secondary IP inside the virtual machine. +message.select.affinity.groups=Please select any anti-affinity groups you want this VM to belong to: +message.no.affinity.groups=You do not have any anti-affinity groups. Please continue to the next step. +label.action.delete.nic=Remove NIC +message.action.delete.nic=Please confirm that want to remove this NIC, which will also remove the associated network from the VM. +changed.item.properties=Changed item properties +confirm.enable.s3=Please fill in the following information to enable support for S3-backed Secondary Storage +confirm.enable.swift=Please fill in the following information to enable support for Swift +error.could.not.change.your.password.because.ldap.is.enabled=Error could not change your password because LDAP is enabled. +error.could.not.enable.zone=Could not enable zone +error.installWizard.message=Something went wrong; you may go back and correct any errors +error.invalid.username.password=Invalid username or password +error.login=Your username/password does not match our records. +error.menu.select=Unable to perform action due to no items being selected. +error.mgmt.server.inaccessible=The Management Server is unaccessible. Please try again later. +error.password.not.match=The password fields do not match +error.please.specify.physical.network.tags=Network offerings is not available until you specify tags for this physical network. +error.session.expired=Your session has expired. +error.something.went.wrong.please.correct.the.following=Something went wrong; please correct the following +error.unable.to.reach.management.server=Unable to reach Management Server +error.unresolved.internet.name=Your internet name cannot be resolved. +label.extractable=Extractable +force.delete.domain.warning=Warning\: Choosing this option will cause the deletion of all child domains and all associated accounts and their resources. +force.delete=Force Delete +force.remove.host.warning=Warning\: Choosing this option will cause Cosmic to forcefully stop all running virtual machines before removing this host from the cluster. +force.remove=Force Remove +force.stop.instance.warning=Warning\: Forcing a stop on this instance should be your last option. It can lead to data loss as well as inconsistent behavior of the virtual machine state. +force.stop=Force Stop +ICMP.code=ICMP Code +ICMP.type=ICMP Type +image.directory=Image Directory +inline=Inline +instances.actions.reboot.label=Reboot instance +label.accept.project.invitation=Accept project invitation +label.account.and.security.group=Account, Security group +label.account.id=Account ID +label.account.name=Account Name +label.account.specific=Account-Specific +label.account=Account +label.accounts=Accounts +label.acquire.new.ip=Acquire New IP +label.acquire.new.secondary.ip=Acquire new secondary IP +label.action.attach.disk.processing=Attaching Disk.... +label.action.attach.disk=Attach Disk +label.action.attach.iso.processing=Attaching ISO.... +label.action.attach.iso=Attach ISO +label.action.cancel.maintenance.mode.processing=Cancelling Maintenance Mode.... +label.action.cancel.maintenance.mode=Cancel Maintenance Mode +label.action.change.password=Change Password +label.action.configure.samlauthorization=Configure SAML SSO Authorization +label.action.change.service.processing=Changing Service.... +label.action.change.service=Change Service +label.action.copy.ISO.processing=Copying ISO.... +label.action.copy.ISO=Copy ISO +label.action.copy.template.processing=Copying Template.... +label.action.copy.template=Copy Template +label.action.create.template.from.vm=Create Template from VM +label.action.create.template.from.volume=Create Template from Volume +label.action.create.template.processing=Creating Template.... +label.action.create.template=Create Template +label.action.create.vm.processing=Creating VM.... +label.action.create.vm=Create VM +label.action.create.volume.processing=Creating Volume.... +label.action.create.volume=Create Volume +label.action.delete.account.processing=Deleting account.... +label.action.delete.account=Delete account +label.action.delete.cluster.processing=Deleting Cluster.... +label.action.delete.cluster=Delete Cluster +label.action.delete.disk.offering.processing=Deleting Disk Offering.... +label.action.delete.disk.offering=Delete Disk Offering +label.action.delete.domain.processing=Deleting Domain.... +label.action.delete.domain=Delete Domain +label.action.delete.firewall.processing=Deleting Firewall.... +label.action.delete.firewall=Delete firewall rule +label.action.delete.ingress.rule.processing=Deleting Ingress Rule.... +label.action.delete.ingress.rule=Delete Ingress Rule +label.action.delete.IP.range.processing=Deleting IP Range.... +label.action.delete.IP.range=Delete IP Range +label.action.delete.ISO.processing=Deleting ISO.... +label.action.delete.ISO=Delete ISO +label.action.delete.load.balancer.processing=Deleting Load Balancer.... +label.action.delete.load.balancer=Delete load balancer rule +label.action.delete.network.processing=Deleting Network.... +label.action.delete.network=Delete Network +label.action.delete.nexusVswitch=Delete Nexus 1000v +label.action.delete.physical.network=Delete physical network +label.action.delete.pod.processing=Deleting Pod.... +label.action.delete.pod=Delete Pod +label.action.delete.primary.storage.processing=Deleting Primary Storage.... +label.action.delete.primary.storage=Delete Primary Storage +label.action.delete.secondary.storage.processing=Deleting Secondary Storage.... +label.action.delete.secondary.storage=Delete Secondary Storage +label.action.delete.security.group.processing=Deleting Security Group.... +label.action.delete.security.group=Delete Security Group +label.action.delete.service.offering.processing=Deleting Service Offering.... +label.action.delete.service.offering=Delete Service Offering +label.action.delete.snapshot.processing=Deleting Snapshot.... +label.action.delete.snapshot=Delete Snapshot +label.action.delete.system.service.offering=Delete System Service Offering +label.action.delete.template.processing=Deleting Template.... +label.action.delete.template=Delete Template +label.action.delete.user.processing=Deleting User.... +label.action.delete.user=Delete User +label.action.delete.volume.processing=Deleting Volume.... +label.action.delete.volume=Delete Volume +label.action.delete.zone.processing=Deleting Zone.... +label.action.delete.zone=Delete Zone +label.action.destroy.instance.processing=Destroying Instance.... +label.action.destroy.instance=Destroy Instance +label.action.destroy.systemvm.processing=Destroying System VM.... +label.action.destroy.systemvm=Destroy System VM +label.action.detach.disk.processing=Detaching Disk.... +label.action.detach.disk=Detach Disk +label.action.detach.iso.processing=Detaching ISO.... +label.action.detach.iso=Detach ISO +label.action.disable.account.processing=Disabling account.... +label.action.disable.account=Disable account +label.action.disable.cluster.processing=Disabling Cluster.... +label.action.disable.cluster=Disable Cluster +label.action.disable.nexusVswitch=Disable Nexus 1000v +label.action.disable.physical.network=Disable physical network +label.action.disable.pod.processing=Disabling Pod.... +label.action.disable.pod=Disable Pod +label.action.disable.static.NAT.processing=Disabling Static NAT.... +label.action.disable.static.NAT=Disable Static NAT +label.action.disable.user.processing=Disabling User.... +label.action.disable.user=Disable User +label.action.disable.zone.processing=Disabling Zone.... +label.action.disable.zone=Disable Zone +label.action.download.ISO=Download ISO +label.action.download.template=Download Template +label.action.download.volume.processing=Downloading Volume.... +label.action.download.volume=Download Volume +label.action.edit.account=Edit account +label.action.edit.disk.offering=Edit Disk Offering +label.action.edit.domain=Edit Domain +label.action.edit.global.setting=Edit Global Setting +label.action.edit.host=Edit Host +label.action.edit.instance=Edit Instance +label.action.edit.ISO=Edit ISO +label.action.edit.network.offering=Edit Network Offering +label.action.edit.network.processing=Editing Network.... +label.action.edit.network=Edit Network +label.action.edit.pod=Edit Pod +label.action.edit.primary.storage=Edit Primary Storage +label.action.edit.resource.limits=Edit Resource Limits +label.action.edit.service.offering=Edit Service Offering +label.action.edit.template=Edit Template +label.action.edit.user=Edit User +label.action.edit.zone=Edit Zone +label.action.enable.account.processing=Enabling account.... +label.action.enable.account=Enable account +label.action.enable.cluster.processing=Enabling Cluster.... +label.action.enable.cluster=Enable Cluster +label.action.enable.maintenance.mode.processing=Enabling Maintenance Mode.... +label.action.enable.maintenance.mode=Enable Maintenance Mode +label.action.enable.nexusVswitch=Enable Nexus 1000v +label.action.enable.physical.network=Enable physical network +label.action.enable.pod.processing=Enabling Pod.... +label.action.enable.pod=Enable Pod +label.action.enable.static.NAT.processing=Enabling Static NAT.... +label.action.enable.static.NAT=Enable Static NAT +label.action.enable.user.processing=Enabling User.... +label.action.enable.user=Enable User +label.action.enable.zone.processing=Enabling Zone.... +label.action.enable.zone=Enable Zone +label.action.expunge.instance=Expunge Instance +label.action.expunge.instance.processing=Expunging Instance.... +label.action.force.reconnect.processing=Reconnecting.... +label.action.force.reconnect=Force Reconnect +label.action.generate.keys.processing=Generate Keys.... +label.action.generate.keys=Generate Keys +label.action.list.nexusVswitch=List Nexus 1000v +label.action.lock.account.processing=Locking account.... +label.action.lock.account=Lock account +label.action.manage.cluster.processing=Managing Cluster.... +label.action.manage.cluster=Manage Cluster +label.action.migrate.instance.processing=Migrating Instance.... +label.action.migrate.instance=Migrate Instance +label.action.migrate.router.processing=Migrating Router.... +label.action.migrate.router=Migrate Router +label.action.migrate.systemvm.processing=Migrating System VM.... +label.action.migrate.systemvm=Migrate System VM +label.action.reboot.instance.processing=Rebooting Instance.... +label.action.reboot.instance=Reboot Instance +label.action.reboot.router.processing=Rebooting Router.... +label.action.reboot.router=Reboot Router +label.action.reboot.systemvm.processing=Rebooting System VM.... +label.action.reboot.systemvm=Reboot System VM +label.action.recurring.snapshot=Recurring Snapshots +label.action.register.iso=Register ISO +label.action.register.template=Register Template from URL +label.action.release.ip.processing=Releasing IP.... +label.action.release.ip=Release IP +label.action.remove.host.processing=Removing Host.... +label.action.remove.host=Remove Host +label.action.reset.password.processing=Resetting Password.... +label.action.reset.password=Reset Password +label.action.resize.volume.processing=Resizing Volume.... +label.action.resize.volume=Resize Volume +label.action.resource.limits=Resource limits +label.action.restore.instance.processing=Restoring Instance.... +label.action.restore.instance=Restore Instance +label.action.start.instance.processing=Starting Instance.... +label.action.start.instance=Start Instance +label.action.start.router.processing=Starting Router.... +label.action.start.router=Start Router +label.action.start.systemvm.processing=Starting System VM.... +label.action.start.systemvm=Start System VM +label.action.stop.instance.processing=Stopping Instance.... +label.action.stop.instance=Stop Instance +label.action.stop.router.processing=Stopping Router.... +label.action.stop.router=Stop Router +label.action.stop.systemvm.processing=Stopping System VM.... +label.action.stop.systemvm=Stop System VM +label.action.take.snapshot.processing=Taking Snapshot.... +label.action.take.snapshot=Take Snapshot +label.action.revert.snapshot.processing=Reverting to Snapshot... +label.action.revert.snapshot=Revert to Snapshot +label.action.unmanage.cluster.processing=Unmanaging Cluster.... +label.action.unmanage.cluster=Unmanage Cluster +label.action.update.OS.preference.processing=Updating OS Preference.... +label.action.update.OS.preference=Update OS Preference +label.action.update.resource.count.processing=Updating Resource Count.... +label.action.update.resource.count=Update Resource Count +label.action.vmsnapshot.create=Take VM Snapshot +label.action.vmsnapshot.delete=Delete VM snapshot +label.action.vmsnapshot.revert=Revert to VM snapshot +label.actions=Actions +label.activate.project=Activate Project +label.active.sessions=Active Sessions +label.add.account.to.project=Add account to project +label.add.account=Add Account +label.add.accounts.to=Add accounts to +label.add.accounts=Add accounts +label.add.ACL=Add ACL +label.add.affinity.group=Add new anti-affinity group +label.add.by.cidr=Add By CIDR +label.add.by.group=Add By Group +label.add.by=Add by +label.add.cluster=Add Cluster +label.add.compute.offering=Add compute offering +label.add.direct.iprange=Add Direct Ip Range +label.add.disk.offering=Add Disk Offering +label.add.domain=Add Domain +label.add.egress.rule=Add egress rule +label.add.firewall=Add firewall rule +label.add.guest.network=Add guest network +label.add.isolated.guest.network=Add Isolated Guest Network +label.add.host=Add Host +label.add.ingress.rule=Add Ingress Rule +label.add.intermediate.certificate=Add intermediate certificate +label.add.ip.range=Add IP Range +label.add.load.balancer=Add Load Balancer +label.add.more=Add More +label.add.network.ACL=Add network ACL +label.add.network.device=Add Network Device +label.add.network.offering=Add network offering +label.add.network=Add Network +label.add.new.gateway=Add new gateway +label.add.new.SRX=Add new SRX +label.add.new.PA=Add new Palo Alto +label.add.new.tier=Add new tier +label.add.NiciraNvp.device=Add Nvp Controller +label.add.NuageVsp.device=Add Nuage Virtualized Services Directory (VSD) +label.add.physical.network=Add physical network +label.add.pod=Add Pod +label.add.port.forwarding.rule=Add port forwarding rule +label.add.primary.storage=Add Primary Storage +label.add.region=Add Region +label.add.resources=Add Resources +label.add.route=Add route +label.add.rule=Add rule +label.add.secondary.storage=Add Secondary Storage +label.add.security.group=Add Security Group +label.add.service.offering=Add Service Offering +label.add.SRX.device=Add SRX device +label.add.PA.device=Add Palo Alto device +label.add.static.nat.rule=Add static NAT rule +label.add.static.route=Add static route +label.add.system.service.offering=Add System Service Offering +label.add.template=Add Template +label.add.to.group=Add to group +label.add.user=Add User +label.add.userdata=Userdata +label.add.vlan=Add VLAN +label.add.vxlan=Add VXLAN +label.add.VM.to.tier=Add VM to tier +label.add.vm=Add VM +label.add.vms.to.lb=Add VM(s) to load balancer rule +label.add.vms=Add VMs +label.add.volume=Add Volume +label.add.vpc=Add VPC +label.add.vpn.customer.gateway=Add VPN Customer Gateway +label.add.VPN.gateway=Add VPN Gateway +label.add.vpn.user=Add VPN user +label.add.zone=Add Zone +label.add=Add +label.adding.cluster=Adding Cluster +label.adding.failed=Adding Failed +label.adding.pod=Adding Pod +label.adding.processing=Adding.... +label.adding.succeeded=Adding Succeeded +label.adding.user=Adding User +label.adding.zone=Adding Zone +label.adding=Adding +label.additional.networks=Additional Networks +label.admin.accounts=Admin Accounts +label.admin=Admin +label.advanced.mode=Advanced Mode +label.advanced.search=Advance Search +label.advanced=Advanced +label.affinity.group=Anti-Affinity Group +label.affinity.groups=Anti-Affinity Groups +label.affinity=Anti-Affinity +label.agent.password=Agent Password +label.agent.port=Agent Port +label.agent.username=Agent Username +label.agree=Agree +label.alert=Alert +label.algorithm=Algorithm +label.allocated=Allocated +label.allocation.state=Allocation State +label.anti.affinity.group=Anti-affinity Group +label.anti.affinity.groups=Anti-affinity Groups +label.anti.affinity=Anti-affinity +label.api.key=API Key +label.api.version=API Version +label.apply=Apply +label.assign.to.load.balancer=Assigning instance to load balancer +label.assign=Assign +label.associated.network.id=Associated Network ID +label.associated.network=Associated Network +label.attached.iso=Attached ISO +label.author.email=Author e-mail +label.author.name=Author name +label.availability.zone=Availability Zone +label.availability=Availability +label.available.public.ips=Available Public IP Addresses +label.available=Available +label.back=Back +label.bandwidth=Bandwidth +label.basic.mode=Basic Mode +label.basic=Basic +label.bootable=Bootable +label.broadcast.domain.range=Broadcast domain range +label.broadcast.domain.type=Broadcast Domain Type +label.broadcast.uri=Broadcast URI +label.by.account=By Account +label.by.availability=By Availability +label.by.domain=By Domain +label.by.end.date=By End Date +label.by.level=By Level +label.by.pod=By Pod +label.by.role=By Role +label.by.start.date=By Start Date +label.by.state=By State +label.by.traffic.type=By Traffic Type +label.by.type.id=By Type ID +label.by.type=By Type +label.by.zone=By Zone +label.bytes.received=Bytes Received +label.bytes.sent=Bytes Sent +label.cancel=Cancel +label.capacity=Capacity +label.capacity.bytes=Capacity Bytes +label.capacity.iops=Capacity IOPS +label.certificate=Server certificate +label.change.ipaddress=Change IP address for NIC +label.change.service.offering=Change service offering +label.change.value=Change value +label.character=Character +label.md5.checksum=MD5 checksum +label.cidr.account=CIDR or Account/Security Group +label.CIDR.list=CIDR list +label.cidr.list=Source CIDR +label.CIDR.of.destination.network=CIDR of destination network +label.cidr=CIDR +label.clean.up=Clean up +label.make.redundant=Make redundant +label.clear.list=Clear list +label.close=Close +label.cloud.console=Cloud Management Console +label.cloud.managed=Cloud.com Managed +label.cluster.name=Cluster Name +label.cluster.type=Cluster Type +label.cluster=Cluster +label.clusters=Clusters +label.clvm=CLVM +label.custom.disk.offering=Custom Disk Offering +label.rbd=RBD +label.rbd.monitor=Ceph monitor +label.rbd.pool=Ceph pool +label.rbd.id=Cephx user +label.rbd.secret=Cephx secret +label.code=Code +label.community=Community +label.compute.and.storage=Compute and Storage +label.compute.offering=Compute offering +label.compute.offerings=Compute Offerings +label.compute=Compute +label.configuration=Configuration +label.configure.network.ACLs=Configure Network ACLs +label.configure.vpc=Configure VPC +label.configure=Configure +label.confirm.password=Confirm password +label.confirmation=Confirmation +label.congratulations=Congratulations\! +label.conserve.mode=Conserve mode +label.console.proxy=Console proxy +label.continue.basic.install=Continue with basic installation +label.continue=Continue +label.corrections.saved=Corrections saved +label.cpu.allocated.for.VMs=CPU Allocated for VMs +label.cpu.allocated=CPU Allocated +label.CPU.cap=CPU Cap +label.cpu.limits=CPU limits +label.cpu.mhz=CPU (in MHz) +label.cpu.utilized=CPU Utilized +label.cpu=CPU +label.create.project=Create project +label.create.ssh.key.pair=Create a SSH Key Pair +label.create.template=Create template +label.create.VPN.connection=Create VPN Connection +label.created.by.system=Created by system +label.created=Created +label.cross.zones=Cross Zones +label.custom.disk.size=Custom Disk Size +label.daily=Daily +label.data.disk.offering=Data Disk Offering +label.date=Date +label.day.of.month=Day of Month +label.day.of.week=Day of Week +label.dead.peer.detection=Dead Peer Detection +label.decline.invitation=Decline invitation +label.dedicated=Dedicated +label.default.use=Default Use +label.default.view=Default View +label.default=Default +label.delete.affinity.group=Delete Anti-Affinity Group +label.delete.gateway=Delete gateway +label.delete.NiciraNvp=Remove Nvp Controller +label.delete.NuageVsp=Remove Nuage VSD +label.delete.project=Delete project +label.delete.SRX=Delete SRX +label.delete.PA=Delete Palo Alto +label.delete.VPN.connection=Delete VPN connection +label.delete.VPN.customer.gateway=Delete VPN Customer Gateway +label.delete.VPN.gateway=Delete VPN Gateway +label.delete.vpn.user=Delete VPN user +label.delete=Delete +label.deleting.failed=Deleting Failed +label.deleting.processing=Deleting.... +label.description=Description +label.destination.physical.network.id=Destination physical network ID +label.destination.zone=Destination Zone +label.destroy.router=Destroy router +label.destroy=Destroy +label.detaching.disk=Detaching Disk +label.details=Details +label.device.id=Device ID +label.devices=Devices +label.DHCP.server.type=DHCP Server Type +label.dhcp=DHCP +label.direct.ips=Shared Network IPs +label.disable.provider=Disable provider +label.disable.vpn=Disable Remote Access VPN +label.disabled=Disabled +label.disabling.vpn.access=Disabling VPN Access +label.disk.allocated=Disk Allocated +label.disk.bytes.read.rate=Disk Read Rate (BPS) +label.disk.bytes.write.rate=Disk Write Rate (BPS) +label.disk.iops.read.rate=Disk Read Rate (IOPS) +label.disk.iops.write.rate=Disk Write Rate (IOPS) +label.disk.offering=Disk Offering +label.disk.provisioningtype=Provisioning Type +label.disk.read.bytes=Disk Read (Bytes) +label.disk.read.io=Disk Read (IO) +label.disk.size.gb=Disk Size (in GB) +label.disk.size=Disk Size +label.disk.total=Disk Total +label.disk.volume=Disk Volume +label.disk.write.bytes=Disk Write (Bytes) +label.disk.write.io=Disk Write (IO) +label.display.text=Display Text +label.dns.1=DNS 1 +label.dns.2=DNS 2 +label.DNS.domain.for.guest.networks=DNS domain for Guest Networks +label.dns=DNS +label.domain.admin=Domain Admin +label.domain.id=Domain ID +label.domain.name=Domain Name +label.domain.router=Domain router +label.domain.suffix=DNS Domain Suffix (i.e., xyz.com) +label.domain=Domain +label.done=Done +label.double.quotes.are.not.allowed=Double quotes are not allowed +label.download.progress=Download Progress +label.drag.new.position=Drag to new position +label.edit.affinity.group=Edit Anti-Affinity Group +label.edit.lb.rule=Edit LB rule +label.edit.network.details=Edit network details +label.edit.project.details=Edit project details +label.edit.tags=Edit tags +label.edit.traffic.type=Edit traffic type +label.edit.vpc=Edit VPC +label.edit=Edit +label.egress.rule=Egress rule +label.egress.rules=Egress rules +label.email=Email +label.enable.provider=Enable provider +label.enable.s3=Enable S3-backed Secondary Storage +label.enable.swift=Enable Swift +label.enable.vpn=Enable Remote Access VPN +label.enabling.vpn.access=Enabling VPN Access +label.enabling.vpn=Enabling VPN +label.end.IP=End IP +label.end.port=End Port +label.end.reserved.system.IP=End Reserved system IP +label.end.vlan=End VLAN +label.end.vxlan=End VXLAN +label.endpoint.or.operation=Endpoint or Operation +label.endpoint=Endpoint +label.enter.token=Enter token +label.error.code=Error Code +label.error=Error +label.ESP.encryption=ESP Encryption +label.ESP.hash=ESP Hash +label.ESP.lifetime=ESP Lifetime (second) +label.ESP.policy=ESP policy +label.esx.host=ESX/ESXi Host +label.example=Example +label.expunge=Expunge +label.external.link=External link +label.failed=Failed +label.featured=Featured +label.fetch.latest=Fetch latest +label.fingerprint=FingerPrint +label.filterBy=Filter by +label.firewall=Firewall +label.first.name=First Name +label.format=Format +label.friday=Friday +label.full.path=Full path +label.full=Full +label.gateway=Gateway +label.general.alerts=General Alerts +label.generating.url=Generating URL +label.gluster.volume=Volume +label.go.step.2=Go to Step 2 +label.go.step.3=Go to Step 3 +label.go.step.4=Go to Step 4 +label.go.step.5=Go to Step 5 +label.group.optional=Group (Optional) +label.group=Group +label.guest.cidr=Guest CIDR +label.guest.end.ip=Guest end IP +label.guest.gateway=Guest Gateway +label.guest.ip.range=Guest IP Range +label.guest.ip=Guest IP Address +label.guest.netmask=Guest Netmask +label.guest.networks=Guest networks +label.guest.start.ip=Guest start IP +label.guest.traffic=Guest Traffic +label.guest.type=Guest Type +label.guest=Guest +label.ha.enabled=HA Enabled +label.help=Help +label.hide.ingress.rule=Hide Ingress Rule +label.hints=Hints +label.host.alerts=Host Alerts +label.host.MAC=Host MAC +label.host.name=Host Name +label.host.tag=Host Tag +label.host.tags=Host Tags +label.host=Host +label.hosts=Hosts +label.hourly=Hourly +label.hypervisor.capabilities=Hypervisor capabilities +label.hypervisor.type=Hypervisor Type +label.hypervisor.version=Hypervisor version +label.hypervisor=Hypervisor +label.id=ID +label.IKE.DH=IKE DH +label.IKE.encryption=IKE Encryption +label.IKE.hash=IKE Hash +label.IKE.lifetime=IKE lifetime (second) +label.IKE.policy=IKE policy +label.info=Info +label.ingress.rule=Ingress Rule +label.initiated.by=Initiated By +label.installWizard.addClusterIntro.subtitle=What is a cluster? +label.installWizard.addClusterIntro.title=Let’s add a cluster +label.installWizard.addHostIntro.subtitle=What is a host? +label.installWizard.addHostIntro.title=Let’s add a host +label.installWizard.addPodIntro.subtitle=What is a pod? +label.installWizard.addPodIntro.title=Let’s add a pod +label.installWizard.addPrimaryStorageIntro.subtitle=What is primary storage? +label.installWizard.addPrimaryStorageIntro.title=Let’s add primary storage +label.installWizard.addSecondaryStorageIntro.subtitle=What is secondary storage? +label.installWizard.addSecondaryStorageIntro.title=Let’s add secondary storage +label.installWizard.addZone.title=Add zone +label.installWizard.addZoneIntro.subtitle=What is a zone? +label.installWizard.addZoneIntro.title=Let’s add a zone +label.installWizard.click.launch=Click the launch button. +label.installWizard.subtitle=This tour will aid you in setting up your Cosmic installation +label.installWizard.title=Hello and Welcome to Cosmic +label.instance.limits=Instance Limits +label.instance.name=Instance Name +label.instance=Instance +label.instances=Instances +label.intermediate.certificate=Intermediate certificate {0} +label.internal.dns.1=Internal DNS 1 +label.internal.dns.2=Internal DNS 2 +label.internal.name=Internal name +label.interval.type=Interval Type +label.introduction.to.cloudstack=Introduction to Cosmic +label.invalid.integer=Invalid Integer +label.invalid.number=Invalid Number +label.invitations=Invitations +label.invite.to=Invite to +label.invite=Invite +label.invited.accounts=Invited accounts +label.ip.address=IP Address +label.ip.allocations=IP Allocations +label.ip.limits=Public IP Limits +label.ip.or.fqdn=IP or FQDN +label.ip.range=IP Range +label.ip.ranges=IP Ranges +label.ip=IP +label.ipaddress=IP Address +label.ips=IPs +label.IPsec.preshared.key=IPsec Preshared-Key +label.is.default=Is Default +label.is.redundant.router=Redundant +label.is.shared=Is Shared +label.is.system=Is System +label.iscsi=iSCSI +label.iso.boot=ISO Boot +label.iso=ISO +label.isolated.networks=Isolated networks +label.isolation.method=Isolation method +label.isolation.mode=Isolation Mode +label.isolation.uri=Isolation URI +label.item.listing=Item listing +label.keep=Keep +label.key=Key +label.keyboard.type=Keyboard type +label.kvm.traffic.label=KVM traffic label +label.label=Label +label.lang.arabic=Arabic +label.lang.brportugese=Brazilian Portugese +label.lang.catalan=Catalan +label.lang.chinese=Chinese (Simplified) +label.lang.dutch=Dutch (Netherlands) +label.lang.english=English +label.lang.french=French +label.lang.german=German +label.lang.italian=Italian +label.lang.japanese=Japanese +label.lang.korean=Korean +label.lang.norwegian=Norwegian +label.lang.polish=Polish +label.lang.russian=Russian +label.lang.spanish=Spanish +label.lang.hungarian=Hungarian +label.last.disconnected=Last Disconnected +label.last.name=Last Name +label.latest.events=Latest events +label.launch.vm=Launch VM +label.launch.zone=Launch zone +label.launch=Launch +label.lb.algorithm.leastconn=Least connections +label.lb.algorithm.roundrobin=Round-robin +label.lb.algorithm.source=Source +label.LB.isolation=LB isolation +label.level=Level +label.linklocal.ip=Link Local IP Address +label.load.balancer=Load Balancer +label.load.balancing.policies=Load balancing policies +label.load.balancing=Load Balancing +label.loading=Loading +label.local.storage.enabled=Enable local storage for User VMs +label.local.storage.enabled.system.vms=Enable local storage for System VMs +label.local.storage=Local Storage +label.local=Local +label.local.file=Local file +label.login=Login +label.logout=Logout +label.saml.enable=Authorize SAML SSO +label.saml.entity=Identity Provider +label.add.LDAP.account=Add LDAP Account +label.LUN.number=LUN \# +label.lun=LUN +label.make.project.owner=Make account project owner +label.manage.resources=Manage Resources +label.management.server=Management Server +label.manage=Manage +label.managed=Managed +label.management.ips=Management IP Addresses +label.management=Management +label.max.cpus=Max. CPU cores +label.max.guest.limit=Max guest limit +label.max.memory=Max. memory (MiB) +label.max.networks=Max. networks +label.max.primary.storage=Max. primary (GiB) +label.max.public.ips=Max. public IPs +label.max.secondary.storage=Max. secondary (GiB) +label.max.snapshots=Max. snapshots +label.max.templates=Max. templates +label.max.vms=Max. user VMs +label.max.volumes=Max. volumes +label.max.vpcs=Max. VPCs +label.maximum=Maximum +label.may.continue=You may now continue. +label.memory.allocated=Memory Allocated +label.memory.limits=Memory limits (MiB) +label.memory.mb=Memory (in MB) +label.memory.total=Memory Total +label.memory.used=Memory Used +label.memory=Memory +label.menu.accounts=Accounts +label.menu.alerts=Alerts +label.menu.all.accounts=All Accounts +label.menu.all.instances=All Instances +label.menu.community.isos=Community ISOs +label.menu.community.templates=Community Templates +label.menu.configuration=Configuration +label.menu.dashboard=Dashboard +label.menu.destroyed.instances=Destroyed Instances +label.menu.disk.offerings=Disk Offerings +label.menu.domains=Domains +label.menu.events=Events +label.menu.featured.isos=Featured ISOs +label.menu.featured.templates=Featured Templates +label.menu.global.settings=Global Settings +label.menu.infrastructure=Infrastructure +label.menu.instances=Instances +label.menu.ipaddresses=IP Addresses +label.menu.isos=ISOs +label.menu.my.accounts=My Accounts +label.menu.my.instances=My Instances +label.menu.my.isos=My ISOs +label.menu.my.templates=My Templates +label.menu.network.offerings=Network Offerings +label.menu.network=Network +label.menu.physical.resources=Physical Resources +label.menu.regions=Regions +label.menu.running.instances=Running Instances +label.menu.security.groups=Security Groups +label.menu.service.offerings=Service Offerings +label.menu.snapshots=Snapshots +label.menu.stopped.instances=Stopped Instances +label.menu.storage=Storage +label.menu.system.service.offerings=System Offerings +label.menu.system.vms=System VMs +label.menu.system=System +label.menu.templates=Templates +label.menu.virtual.appliances=Virtual Appliances +label.menu.virtual.resources=Virtual Resources +label.menu.volumes=Volumes +label.menu.sshkeypair=SSH KeyPair +label.metrics=Metrics +label.metrics.allocated=Allocated +label.metrics.clusters=Clusters +label.metrics.cpu.allocated=CPU Allocation +label.metrics.cpu.max.dev=Deviation +label.metrics.cpu.total=Total +label.metrics.cpu.usage=CPU Usage +label.metrics.cpu.used.avg=Used +label.metrics.disk=Disk +label.metrics.disk.iops.total=IOPS +label.metrics.disk.read=Read +label.metrics.disk.size=Size +label.metrics.disk.storagetype=Type +label.metrics.disk.usage=Disk Usage +label.metrics.disk.used=Used +label.metrics.disk.total=Total +label.metrics.disk.allocated=Allocated +label.metrics.disk.unallocated=Unallocated +label.metrics.disk.write=Write +label.metrics.hosts=Hosts +label.metrics.memory.allocated=Mem Allocation +label.metrics.memory.max.dev=Deviation +label.metrics.memory.total=Total +label.metrics.memory.usage=Mem Usage +label.metrics.memory.used.avg=Used +label.metrics.name=Name +label.metrics.network.usage=Network Usage +label.metrics.network.read=Read +label.metrics.network.write=Write +label.metrics.num.cpu.cores=Cores +label.metrics.property=Property +label.metrics.scope=Scope +label.metrics.state=State +label.metrics.storagepool=Storage Pool +label.metrics.vm.name=VM Name +label.migrate.instance.to.host=Migrate instance to another host +label.migrate.instance.to.ps=Migrate instance to another primary storage +label.migrate.instance.to=Migrate instance to +label.migrate.router.to=Migrate Router to +label.migrate.systemvm.to=Migrate System VM to +label.migrate.to.host=Migrate to host +label.migrate.to.storage=Migrate to storage +label.migrate.volume.to.primary.storage=Migrate volume to another primary storage +label.migrate.volume=Migrate Volume +label.minimum=Minimum +label.minute.past.hour=minute(s) past the hour +label.monday=Monday +label.monthly=Monthly +label.more.templates=More Templates +label.move.down.row=Move down one row +label.move.to.bottom=Move to bottom +label.move.to.top=Move to top +label.move.up.row=Move up one row +label.my.account=My Account +label.my.network=My network +label.my.templates=My templates +label.na=N/A +label.name.optional=Name (Optional) +label.name=Name +label.nat.port.range=NAT Port Range +label.netmask=Netmask +label.network.ACL.total=Network ACL Total +label.network.ACL=Network ACL +label.network.ACLs=Network ACLs +label.network.desc=Network Desc +label.network.device.type=Network Device Type +label.network.device=Network Device +label.network.domain.text=Network domain +label.network.domain=Network Domain +label.network.id=Network ID +label.network.label.display.for.blank.value=Use default gateway +label.network.limits=Network limits +label.network.name=Network Name +label.network.offering.display.text=Network Offering Display Text +label.network.offering.id=Network Offering ID +label.network.offering.name=Network Offering Name +label.network.offering=Network Offering +label.network.rate.megabytes=Network Rate (MB/s) +label.network.rate=Network Rate (Mb/s) +label.network.read=Network Read +label.network.service.providers=Network Service Providers +label.network.type=Network Type +label.network.write=Network Write +label.network=Network +label.networking.and.security=Networking and security +label.networks=Networks +label.new.password=New Password +label.new.project=New Project +label.new.ssh.key.pair=New SSH Key Pair +label.new.vm=New VM +label.new=New +label.next=Next +label.nexusVswitch=Nexus 1000v +label.nfs.server=NFS Server +label.nfs.storage=NFS Storage +label.nfs=NFS +label.nic.adapter.type=NIC adapter type +label.nicira.controller.address=Controller Address +label.nicira.l2gatewayserviceuuid=L2 Gateway Service Uuid +label.nicira.l3gatewayserviceuuid=L3 Gateway Service Uuid +label.nicira.transportzoneuuid=Transport Zone Uuid +label.nics=NICs +label.no.actions=No Available Actions +label.no.alerts=No Recent Alerts +label.no.data=No data to show +label.no.errors=No Recent Errors +label.no.isos=No available ISOs +label.no.items=No Available Items +label.no.security.groups=No Available Security Groups +label.no.thanks=No thanks +label.no=No +label.none=None +label.not.found=Not Found +label.notifications=Notifications +label.num.cpu.cores=\# of CPU Cores +label.number.of.clusters=Number of Clusters +label.number.of.hosts=Number of Hosts +label.number.of.pods=Number of Pods +label.number.of.system.vms=Number of System VMs +label.number.of.virtual.routers=Number of Virtual Routers +label.number.of.zones=Number of Zones +label.numretries=Number of Retries +label.ocfs2=OCFS2 +label.offer.ha=Offer HA +label.ok=OK +label.optional=Optional +label.order=Order +label.os.preference=OS Preference +label.os.type=OS Type +label.ovm3.vip=Master Vip IP +label.ovm3.pool=Native Pooling +label.ovm3.cluster=Native Clustering +label.ovm3.traffic.label=OVM3 traffic label +label.owned.public.ips=Owned Public IP Addresses +label.owner.account=Owner Account +label.owner.domain=Owner Domain +label.PA.log.profile=Palo Alto Log Profile +label.PA.threat.profile=Palo Alto Threat Profile +label.parent.domain=Parent Domain +label.password.enabled=Password Enabled +label.password=Password +label.path=Path +label.perfect.forward.secrecy=Perfect Forward Secrecy +label.physical.network.ID=Physical network ID +label.physical.network=Physical Network +label.PING.CIFS.password=PING CIFS password +label.PING.CIFS.username=PING CIFS username +label.PING.dir=PING Directory +label.PING.storage.IP=PING storage IP +label.please.wait=Please Wait +label.plugin.details=Plugin details +label.plugins=Plugins +label.pod.name=Pod name +label.pod=Pod +label.pods=Pods +label.port.forwarding.policies=Port forwarding policies +label.port.forwarding=Port Forwarding +label.port.range=Port Range +label.PreSetup=PreSetup +label.prev=Prev +label.previous=Previous +label.primary.allocated=Primary Storage Allocated +label.primary.network=Primary Network +label.primary.storage.count=Primary Storage Pools +label.primary.storage.limits=Primary Storage limits (GiB) +label.primary.storage=Primary Storage +label.primary.used=Primary Storage Used +label.private.Gateway=Private Gateway +label.private.interface=Private Interface +label.private.ip.range=Private IP Range +label.private.ip=Private IP Address +label.private.ips=Private IP Addresses +label.private.key=Private Key +label.private.network=Private network +label.private.port=Private Port +label.private.zone=Private Zone +label.privatekey=PKCS\#8 Private Key +label.project.dashboard=Project dashboard +label.project.id=Project ID +label.project.invite=Invite to project +label.project.name=Project name +label.project.view=Project View +label.project=Project +label.projects=Projects +label.protocol=Protocol +label.provider=Provider +label.providers=Providers +label.public.interface=Public Interface +label.public.ip=Public IP Address +label.public.ips=Public IP Addresses +label.public.key=Public Key +label.public.network=Public network +label.public.port=Public Port +label.public.traffic=Public traffic +label.public.zone=Public Zone +label.public=Public +label.purpose=Purpose +label.quickview=Quickview +label.reboot=Reboot +label.recent.errors=Recent Errors +label.redundant.router.capability=Redundant router capability +label.redundant.router=Redundant Router +label.redundant.vpc=Redundant VPC +label.redundant.state=Redundant state +label.refresh=Refresh +label.region=Region +label.related=Related +label.remind.later=Remind me later +label.remove.ACL=Remove ACL +label.remove.egress.rule=Remove egress rule +label.remove.from.load.balancer=Removing instance from load balancer +label.remove.ingress.rule=Remove ingress rule +label.remove.ip.range=Remove IP range +label.remove.pf=Remove port forwarding rule +label.remove.project.account=Remove account from project +label.remove.region=Remove Region +label.remove.rule=Remove rule +label.remove.ssh.key.pair=Remove SSH Key Pair +label.remove.static.nat.rule=Remove static NAT rule +label.remove.static.route=Remove static route +label.remove.tier=Remove tier +label.remove.vm.from.lb=Remove VM from load balancer rule +label.remove.vpc=Remove VPC +label.removing.user=Removing User +label.removing=Removing +label.required=Required +label.reserved.system.gateway=Reserved system gateway +label.reserved.system.ip=Reserved System IP +label.reserved.system.netmask=Reserved system netmask +label.reset.ssh.key.pair=Reset SSH Key Pair +label.reset.ssh.key.pair.on.vm=Reset SSH Key Pair on VM +label.reset.VPN.connection=Reset VPN connection +label.resize.new.offering.id=New Offering +label.resize.new.size=New Size (GB) +label.resize.shrink.ok=Shrink OK +label.resource.limits=Resource Limits +label.resource.state=Resource state +label.resource=Resource +label.resources=Resources +label.restart.network=Restart network +label.restart.required=Restart required +label.restart.vpc=Restart VPC +message.restart.vpc.remark=Please confirm that you want to restart the VPC

Remark: making a non-redundant VPC redundant will force a clean up. The networks will not be available for a couple of minutes.

+label.restore=Restore +label.retry.interval=Retry Interval +label.review=Review +label.revoke.project.invite=Revoke invitation +label.role=Role +label.root.certificate=Root certificate +label.root.disk.controller=Root disk controller +label.root.disk.offering=Root Disk Offering +label.rules=Rules +label.running.vms=Running VMs +label.s3.access_key=Access Key +label.s3.bucket=Bucket +label.s3.connection_timeout=Connection Timeout +label.s3.endpoint=Endpoint +label.s3.max_error_retry=Max Error Retry +label.s3.secret_key=Secret Key +label.s3.socket_timeout=Socket Timeout +label.s3.use_https=Use HTTPS +label.saturday=Saturday +label.save.and.continue=Save and continue +label.save=Save +label.saving.processing=Saving.... +label.scope=Scope +label.search=Search +label.secondary.storage.count=Secondary Storage Pools +label.secondary.storage.limits=Secondary Storage limits (GiB) +label.secondary.storage.vm=Secondary storage VM +label.secondary.storage=Secondary Storage +label.secondary.used=Secondary Storage Used +label.secret.key=Secret Key +label.security.group.name=Security Group Name +label.security.group=Security Group +label.security.groups.enabled=Security Groups Enabled +label.security.groups=Security Groups +label.select-view=Select view +label.select.a.template=Select a template +label.select.a.zone=Select a zone +label.select.instance.to.attach.volume.to=Select instance to attach volume to +label.select.instance=Select instance +label.select.iso.or.template=Select ISO or template +label.select.offering=Select offering +label.select.project=Select Project +label.select.tier=Select Tier +label.select.vm.for.static.nat=Select VM for static NAT +label.select=Select +label.sent=Sent +label.server=Server +label.service.capabilities=Service Capabilities +label.service.offering=Service Offering +label.session.expired=Session Expired +label.set.up.zone.type=Set up zone type +label.setup.network=Set up Network +label.setup.zone=Set up Zone +label.setup=Setup +label.shared=Shared +label.SharedMountPoint=SharedMountPoint +label.show.ingress.rule=Show Ingress Rule +label.shutdown.provider=Shutdown provider +label.site.to.site.VPN=Site-to-site VPN +label.size=Size +label.skip.guide=I have used Cosmic before, skip this guide +label.snapshot.limits=Snapshot Limits +label.snapshot.name=Snapshot Name +label.snapshot.s=Snapshots +label.snapshot.schedule=Set up Recurring Snapshot +label.snapshot=Snapshot +label.snapshots=Snapshots +label.source.nat=Source NAT +label.specify.IP.ranges=Specify IP ranges +label.specify.vlan=Specify VLAN +label.specify.vxlan=Specify VXLAN +label.SR.name=SR Name-Label +label.srx=SRX +label.ssh.key.pair=SSH Key Pair +label.ssh.key.pair.details=SSH Key Pair Details +label.PA=Palo Alto +label.start.IP=Start IP +label.start.port=Start Port +label.start.reserved.system.IP=Start Reserved system IP +label.start.vlan=Start VLAN +label.start.vxlan=Start VXLAN +label.state=State +label.static.nat.enabled=Static NAT Enabled +label.static.nat.to=Static NAT to +label.static.nat.vm.details=Static NAT VM Details +label.static.nat=Static NAT +label.statistics=Statistics +label.status=Status +label.step.1.title=Step 1\: Select a Template +label.step.1=Step 1 +label.step.2.title=Step 2\: Service Offering +label.step.2=Step 2 +label.step.3.title=Step 3\: Select a Disk Offering +label.step.3=Step 3 +label.step.4.title=Step 4\: Network +label.step.4=Step 4 +label.step.5.title=Step 5\: Review +label.step.5=Step 5 +label.stickiness=Stickiness +label.sticky.cookie-name=Cookie name +label.sticky.domain=Domain +label.sticky.expire=Expires +label.sticky.holdtime=Hold time +label.sticky.indirect=Indirect +label.sticky.length=Length +label.sticky.mode=Mode +label.sticky.nocache=No cache +label.sticky.postonly=Post only +label.sticky.prefix=Prefix +label.sticky.request-learn=Request learn +label.sticky.tablesize=Table size +label.stop=Stop +label.stopped.vms=Stopped VMs +label.storage.tags=Storage Tags +label.storage.traffic=Storage Traffic +label.storage.type=Storage Type +label.qos.type=QoS Type +label.cache.mode=Write-cache Type +label.storage=Storage +label.subdomain.access=Subdomain Access +label.submit=Submit +label.submitted.by=[Submitted by\: ] +label.succeeded=Succeeded +label.sunday=Sunday +label.super.cidr.for.guest.networks=Super CIDR for Guest Networks +label.supported.services=Supported Services +label.supported.source.NAT.type=Supported Source NAT type +label.suspend.project=Suspend Project +label.system.capacity=System Capacity +label.system.offering=System Offering +label.system.service.offering=System Service Offering +label.system.vm.type=System VM Type +label.system.vm=System VM +label.system.vms=System VMs +label.system.wide.capacity=System-wide capacity +label.tagged=Tagged +label.tags=Tags +label.target.iqn=Target IQN +label.task.completed=Task completed +label.template.limits=Template Limits +label.template=Template +label.TFTP.dir=TFTP Directory +label.theme.default=Default Theme +label.theme.grey=Custom - Grey +label.theme.lightblue=Custom - Light Blue +label.thursday=Thursday +label.tier.details=Tier details +label.tier=Tier +label.time.zone=Timezone +label.time=Time +label.timeout.in.second=Timeout (seconds) +label.timeout=Timeout +label.timezone=Timezone +label.token=Token +label.total.CPU=Total CPU +label.total.cpu=Total CPU +label.total.hosts=Total Hosts +label.total.memory=Total Memory +label.total.of.ip=Total of IP Addresses +label.total.of.vm=Total of VMs +label.total.storage=Total Storage +label.total.vms=Total VMs +label.traffic.label=Traffic label +label.traffic.type=Traffic Type +label.traffic.types=Traffic Types +label.tuesday=Tuesday +label.type.id=Type ID +label.type=Type +label.unavailable=Unavailable +label.unlimited=Unlimited +label.untagged=Untagged +label.update.project.resources=Update project resources +label.update.ssl.cert=SSL Certificate +label.update.ssl=SSL Certificate +label.updating=Updating +label.upload.from.local=Upload from Local +label.upload.template.from.local=Upload Template from Local +label.upload.volume=Upload volume +label.upload.volume.from.local=Upload Volume from Local +label.upload.volume.from.url=Upload volume from URL +label.upload=Upload +label.url=URL +label.usage.interface=Usage Interface +label.use.vm.ip=Use VM IP\: +label.used=Used +label.user=User +label.username=Username +label.users=Users +label.value=Value +label.vcdcname=vCenter DC name +label.vcenter.cluster=vCenter Cluster +label.vcenter.datacenter=vCenter Datacenter +label.vcenter.datastore=vCenter Datastore +label.vcenter.host=vCenter Host +label.vcenter.password=vCenter Password +label.vcenter.username=vCenter Username +label.vcipaddress=vCenter IP Address +label.version=Version +label.view.all=View all +label.view.console=View console +label.view.more=View more +label.view=View +label.viewing=Viewing +label.virtual.appliance=Virtual Appliance +label.virtual.appliances=Virtual Appliances +label.virtual.machines=Virtual Machines +label.virtual.machine=Virtual Machine +label.virtual.network=Virtual Network +label.virtual.router=Virtual Router +label.virtual.routers=Virtual Routers +label.vlan.id=VLAN/VNI ID +label.vlan.range=VLAN/VNI Range +label.vlan=VLAN/VNI +label.vnet=VLAN/VNI +label.vnet.id=VLAN/VNI ID +label.vxlan.id=VXLAN ID +label.vxlan.range=VXLAN Range +label.vxlan=VXLAN +label.vm.add=Add Instance +label.vm.destroy=Destroy +label.vm.display.name=VM display name +label.vm.name=VM name +label.vm.reboot=Reboot +label.vm.start=Start +label.vm.state=VM state +label.vm.stop=Stop +label.VMFS.datastore=VMFS datastore +label.vmfs=VMFS +label.VMs.in.tier=VMs in tier +label.vms=VMs +label.vmsnapshot.current=isCurrent +label.vmsnapshot.memory=Snapshot memory +label.vmsnapshot.parentname=Parent +label.vmsnapshot.type=Type +label.vmsnapshot=VM Snapshots +label.volgroup=Volume Group +label.volume.limits=Volume Limits +label.volume.name=Volume Name +label.volume=Volume +label.volumes=Volumes +label.vpc.id=VPC ID +label.VPC.router.details=VPC router details +label.vpc=VPC +label.VPN.connection=VPN Connection +label.VPN.customer.gateway=VPN Customer Gateway +label.vpn.customer.gateway=VPN Customer Gateway +label.VPN.gateway=VPN Gateway +label.vpn=VPN +label.vsmctrlvlanid=Control VLAN ID +label.vsmpktvlanid=Packet VLAN ID +label.vsmstoragevlanid=Storage VLAN ID +label.vsphere.managed=vSphere Managed +label.waiting=Waiting +label.warn=Warn +label.warning=Warning +label.wednesday=Wednesday +label.weekly=Weekly +label.welcome.cloud.console=Welcome to Management Console +label.welcome=Welcome +label.what.is.cloudstack=What is Cosmic? +label.xenserver.traffic.label=XenServer traffic label +label.yes=Yes +label.zone.details=Zone details +label.zone.id=Zone ID +label.zone.step.1.title=Step 1\: Select a Network +label.zone.step.2.title=Step 2\: Add a Zone +label.zone.step.3.title=Step 3\: Add a Pod +label.zone.step.4.title=Step 4\: Add an IP range +label.zone.type=Zone Type +label.zone.wide=Zone-Wide +label.zone=Zone +label.zones=Zones +label.zoneWizard.trafficType.guest=Guest\: Traffic between end-user virtual machines +label.zoneWizard.trafficType.management=Management\: Traffic between Cosmic\\\\'s internal resources, including any components that communicate with the Management Server, such as hosts and Cosmic system VMs +label.zoneWizard.trafficType.public=Public\: Traffic between the internet and virtual machines in the cloud. +label.zoneWizard.trafficType.storage=Storage\: Traffic between primary and secondary storage servers, such as VM templates and snapshots +label.ldap.group.name=LDAP Group +label.password.reset.confirm=Password has been reset to +label.provider=Provider +label.resetVM=Reset VM +label.assign.instance.another=Assign Instance to Another Account +label.network.addVM=Add network to VM +label.set.default.NIC=Set default NIC +label.Xenserver.Tools.Version61plus=Original XS Version is 6.1\+ +label.supportsstrechedl2subnet=Supports Streched L2 Subnet +label.menu.vpc.offerings=VPC Offerings +label.vpc.offering=VPC Offering +label.regionlevelvpc=Region Level VPC +label.add.vpc.offering=Add VPC Offering +label.distributedrouter=Distributed Router +label.vpc.offering.details=VPC offering details +label.disable.vpc.offering=Disable VPC offering +label.enable.vpc.offering=Enable VPC offering +label.remove.vpc.offering=Remove VPC offering +label.vpc.distributedvpcrouter=Distributed VPC Router +label.vpc.supportsregionlevelvpc=Supports Region Level VPC +label.dynamically.scalable=Dynamically Scalable +label.instance.scaled.up=Instance scaled to the requested offering +label.tag.key=Tag Key +label.tag.value=Tag Value +label.ipv6.address=IPv6 IP Address +label.ipv6.gateway=IPv6 Gateway +label.ipv6.CIDR=IPv6 CIDR +label.VPC.limits=VPC limits +label.edit.region=Edit Region +label.gslb.domain.name=GSLB Domain Name +label.add.gslb=Add GSLB +label.gslb.servicetype=Service Type +label.gslb.details=GSLB details +label.gslb.delete=Delete GSLB +label.portable.ip.ranges=Portable IP Ranges +label.add.portable.ip.range=Add Portable IP Range +label.delete.portable.ip.range=Delete Portable IP Range +label.portable.ip.range.details=Portable IP Range details +label.portable.ips=Portable IPs +label.gslb.assigned.lb=Assigned load balancing +label.gslb.assigned.lb.more=Assign more load balancing +label.gslb.lb.rule=Load balancing rule +label.gslb.lb.details=Load balancing details +label.gslb.lb.remove=Remove load balancing from this GSLB +label.enable.autoscale=Enable Autoscale +label.disable.autoscale=Disable Autoscale +label.min.instances=Min Instances +label.max.instances=Max Instances +label.show.advanced.settings=Show advanced settings +label.polling.interval.sec=Polling Interval (in sec) +label.quiet.time.sec=Quiet Time (in sec) +label.usage.type=Usage Type +label.usage.unit=Unit +label.quota.value=Quota Value +label.quota.description=Quota Description +label.quota.configuration=Quota Configuration +label.quota.configure=Configure Quota +label.quota.remove=Remove Quota +label.quota.totalusage=Total Usage +label.quota.balance=Balance +label.quota.minbalance=Min Balance +label.quota.enforcequota=Enforce Quota +label.quota.summary=Summary +label.quota.fullsummary=All Accounts +label.quota.tariff=Tariff +label.quota.state=State +label.quota.startdate=Start Date +label.quota.enddate=End Date +label.quota.total=Total +label.quota.startquota=Start Quota +label.quota.endquota=End Quota +label.quota.type.name=Usage Type +label.quota.type.unit=Usage Unit +label.quota.usage=Quota Consumption +label.quota.add.credits=Add Credits +label.quota.email.template=Email Template +label.quota.statement=Statement +label.quota.statement.balance=Quota Balance +label.quota.statement.quota=Quota Usage +label.quota.statement.tariff=Quota Tariff +label.quota.tariff.value=Tariff Value +label.quota.tariff.edit=Edit Tariff +label.quota.tariff.effectivedate=Effective Date +label.quota.date=Date +label.quota.dates=Update Dates +label.quota.credit=Credit +label.quota.credits=Credits +label.quota.value=Quota Value +label.quota.statement.bydates=Statement +label.quota.email.subject=Subject +label.quota.email.body=Body +label.quota.email.lastupdated=Last Update +label.destroy.vm.graceperiod=Destroy VM Grace Period +label.SNMP.community=SNMP Community +label.SNMP.port=SNMP Port +label.resource.name=Resource Name +label.reource.id=Resource ID +label.vnmc.devices=VNMC Devices +label.add.vnmc.provider=Add VNMC provider +label.enable.vnmc.provider=Enable VNMC provider +label.add.vnmc.device=Add VNMC device +label.ciscovnmc.resource.details=CiscoVNMC resource details +label.delete.ciscovnmc.resource=Delete CiscoVNMC resource +label.enable.vnmc.device=Enable VNMC device +label.disbale.vnmc.device=Disable VNMC device +label.disable.vnmc.provider=Disable VNMC provider +label.services=Services +label.secondary.staging.store=Secondary Staging Store +label.release.account=Release from Account +label.release.account.lowercase=Release from account +label.vlan.vni.ranges=VLAN/VNI Range(s) +label.dedicated.vlan.vni.ranges=Dedicated VLAN/VNI Ranges +label.dedicate.vlan.vni.range=Dedicate VLAN/VNI Range +label.vlan.vni.range=VLAN/VNI Range +label.vlan.range.details=VLAN Range details +label.release.dedicated.vlan.range=Release dedicated VLAN range +label.broadcat.uri=Broadcast URI +label.ipv4.cidr=IPv4 CIDR +label.guest.network.details=Guest network details +label.ipv4.gateway=IPv4 Gateway +label.release.dedicated.vlan.range=Release dedicated VLAN range +label.vlan.ranges=VLAN Range(s) +label.virtual.appliance.details=Virtual applicance details +label.start.lb.vm=Start LB VM +label.stop.lb.vm=Stop LB VM +label.migrate.lb.vm=Migrate LB VM +label.vpc.virtual.router=VPC Virtual Router +label.ovs=OVS +label.gslb.service=GSLB service +label.gslb.service.public.ip=GSLB service Public IP +label.gslb.service.private.ip=GSLB service Private IP +label.tftp.root.directory=Tftp root directory +label.dc.name=DC Name +label.vcenter=vcenter +label.dedicate.zone=Dedicate Zone +label.zone.dedicated=Zone Dedicated +label.release.dedicated.zone=Release Dedicated Zone +label.ipv6.dns1=IPv6 DNS1 +label.ipv6.dns2=IPv6 DNS2 +label.system.vm.details=System VM details +label.system.vm.scaled.up=System VM Scaled Up +label.console.proxy.vm=Console Proxy VM +label.settings=Settings +label.requires.upgrade=Requires Upgrade +label.upgrade.router.newer.template=Upgrade Router to Use Newer Template +label.router.vm.scaled.up=Router VM Scaled Up +label.total.virtual.routers=Total of Virtual Routers +label.upgrade.required=Upgrade is required +label.virtual.routers.group.zone=Virtual Routers group by zone +label.total.virtual.routers.upgrade=Total of Virtual Routers that require upgrade +label.virtual.routers.group.pod=Virtual Routers group by pod +label.virtual.routers.group.cluster=Virtual Routers group by cluster +label.zone.lower=zone +label.virtual.routers.group.account=Virtual Routers group by account +label.srx.details=SRX details +label.palo.alto.details=Palo Alto details +label.added.nicira.nvp.controller=Added new Nicira NVP Controller +label.nicira.nvp.details=Nicira NVP details +label.dedicate=Dedicate +label.dedicate.pod=Dedicate Pod +label.pod.dedicated=Pod Dedicated +label.release.dedicated.pod=Release Dedicated Pod +label.override.public.traffic=Override Public-Traffic +label.public.traffic.vswitch.type=Public Traffic vSwitch Type +label.public.traffic.vswitch.name=Public Traffic vSwitch Name +label.override.guest.traffic=Override Guest-Traffic +label.guest.traffic.vswitch.type=Guest Traffic vSwitch Type +label.guest.traffic.vswitch.name=Guest Traffic vSwitch Name +label.cisco.nexus1000v.ip.address=Nexus 1000v IP Address +label.cisco.nexus1000v.username=Nexus 1000v Username +label.cisco.nexus1000v.password=Nexus 1000v Password +label.dedicate.cluster=Dedicate Cluster +label.release.dedicated.cluster=Release Dedicated Cluster +label.dedicate.host=Dedicate Host +label.release.dedicated.host=Release Dedicated Host +label.number.of.cpu.sockets=The Number of CPU Sockets +label.blades=Blades +label.chassis=Chassis +label.blade.id=Blade ID +label.associated.profile=Associated Profile +label.refresh.blades=Refresh Blades +label.instanciate.template.associate.profile.blade=Instanciate Template and Associate Profile to Blade +label.select.template=Select Template +label.profile=Profile +label.delete.profile=Delete Profile +label.disassociate.profile.blade=Disassociate Profile from Blade +label.secondary.storage.details=Secondary storage details +label.secondary.staging.store.details=Secondary Staging Store details +label.add.nfs.secondary.staging.store=Add NFS Secondary Staging Store +label.delete.secondary.staging.store=Delete Secondary Staging Store +label.ipv4.start.ip=IPv4 Start IP +label.ipv4.end.ip=IPv4 End IP +label.ipv6.start.ip=IPv6 Start IP +label.ipv6.end.ip=IPv6 End IP +label.vm.password=Password of the VM is +label.group.by.zone=Group by zone +label.group.by.pod=Group by pod +label.group.by.cluster=Group by cluster +label.group.by.account=Group by account +label.no.grouping=(no grouping) +label.create.nfs.secondary.staging.storage=Create NFS Secondary Staging Store +label.username.lower=username +label.password.lower=password +label.email.lower=email +label.firstname.lower=firstname +label.lastname.lower=lastname +label.domain.lower=domain +label.account.lower=account +label.type.lower=type +label.rule.number=Rule Number +label.action=Action +label.name.lower=name +label.change.affinity=Change Anti-Affinity +label.persistent=Persistent +label.broadcasturi=broadcasturi +label.network.cidr=Network CIDR +label.reserved.ip.range=Reserved IP Range +label.autoscale=AutoScale +label.health.check=Health Check +label.public.load.balancer.provider=Public Load Balancer Provider +label.add.isolated.network=Add Isolated Network +label.add.isolated.guest.network=Add Isolated Guest Network +label.vlan.only=VLAN +label.secondary.isolated.vlan.id=Secondary Isolated VLAN ID +label.ipv4.netmask=IPv4 Netmask +label.custom=Custom +label.disable.network.offering=Disable network offering +label.enable.network.offering=Enable network offering +label.remove.network.offering=Remove network offering +label.system.offering.for.router=System Offering for Router +label.mode=Mode +label.associate.public.ip=Associate Public IP +label.acl=ACL +label.user.data=User Data +label.virtual.networking=Virtual Networking +label.allow=Allow +label.deny=Deny +label.default.egress.policy=Default egress policy +label.xenserver.tools.version.61.plus=Original XS Version is 6.1\+ +label.gpu=GPU +label.vgpu.type=vGPU type +label.vgpu.video.ram=Video RAM +label.vgpu.max.resolution=Max resolution +label.vgpu.max.vgpu.per.gpu=vGPUs per GPU +label.vgpu.remaining.capacity=Remaining capacity +label.routing.host=Routing Host +label.usage.server=Usage Server +label.user.vm=User VM +label.resource.limit.exceeded=Resource Limit Exceeded +label.direct.attached.public.ip=Direct Attached Public IP +label.usage.sanity.result=Usage Sanity Result +label.select.region=Select region +label.info.upper=INFO +label.warn.upper=WARN +label.error.upper=ERROR +label.event.deleted=Event Deleted +label.add.ciscoASA1000v=Add CiscoASA1000v Resource +label.delete.ciscoASA1000v=Delete CiscoASA1000v +label.inside.port.profile=Inside Port Profile +label.archive=Archive +label.event.archived=Event Archived +label.alert.details=Alert details +label.alert.deleted=Alert Deleted +label.alert.archived=Alert Archived +label.volume.details=Volume details +label.volume.migrated=Volume migrated +label.storage.pool=Storage Pool +label.enable.host=Enable Host +label.disable.host=Disable Host +label.copying.iso=Copying ISO +label.add.internal.lb=Add Internal LB +label.internal.lb.details=Internal LB details +label.delete.internal.lb=Delete Internal LB +label.remove.vm.load.balancer=Remove VM from load balancer +label.add.acl.list=Add ACL List +label.add.list.name=ACL List Name +label.add.network.acl.list=Add Network ACL List +label.delete.acl.list=Delete ACL List +label.acl.replaced=ACL replaced +label.ipv4.dns1=IPv4 DNS1 +label.ipv4.dns2=IPv4 DNS2 +label.protocol.number=Protocol Number +label.edit.acl.rule=Edit ACL rule +label.source.ip.address=Source IP Address +label.source.port=Source Port +label.instance.port=Instance Port +label.assigned.vms=Assigned VMs +label.replace.acl=Replace ACL +label.source.nat.supported=SourceNAT Supported +label.acl.name=ACL Name +label.acl.id=ACL ID +label.passive=Passive +label.replace.acl.list=Replace ACL List +label.vswitch.name=vSwitch Name +label.vSwitch.type=vSwitch Type +label.ping.path=Ping Path +label.response.timeout.in.sec=Response Timeout (in sec) +label.health.check.interval.in.sec=Health Check Interval (in sec) +label.healthy.threshold=Healthy Threshold +label.unhealthy.threshold=Unhealthy Threshold +label.other=Other +label.vm.id=VM ID +label.vnmc=VNMC +label.scale.up.policy=SCALE UP POLICY +label.counter=Counter +label.operator=Operator +label.threshold=Threshold +label.load.balancer.type=Load Balancer Type +label.vgpu=VGPU +label.sticky.name=Sticky Name +label.stickiness.method=Stickiness method +label.gslb=GSLB +label.portable.ip=Portable IP +label.internallbvm=InternalLbVm +label.agent.state=Agent State +label.duration.in.sec=Duration (in sec) +managed.state=Managed State +message.acquire.new.ip.vpc=Please confirm that you would like to acquire a new IP for this VPC. +message.acquire.new.ip=Please confirm that you would like to acquire a new IP for this network. +message.acquire.public.ip=Please select a zone from which you want to acquire your new IP from. +message.action.cancel.maintenance.mode=Please confirm that you want to cancel this maintenance. +message.action.cancel.maintenance=Your host has been successfully canceled for maintenance. This process can take up to several minutes. +message.action.change.service.warning.for.instance=Your instance must be stopped before attempting to change its current service offering. +message.action.change.service.warning.for.router=Your router must be stopped before attempting to change its current service offering. +message.action.delete.cluster=Please confirm that you want to delete this cluster. +message.action.delete.disk.offering=Please confirm that you want to delete this disk offering. +message.action.delete.domain=Please confirm that you want to delete this domain. +message.action.delete.external.firewall=Please confirm that you would like to remove this external firewall. Warning\: If you are planning to add back the same external firewall, you must reset usage data on the device. +message.action.delete.external.load.balancer=Please confirm that you would like to remove this external load balancer. Warning\: If you are planning to add back the same external load balancer, you must reset usage data on the device. +message.action.delete.ingress.rule=Please confirm that you want to delete this ingress rule. +message.action.delete.ISO.for.all.zones=The ISO is used by all zones. Please confirm that you want to delete it from all zones. +message.action.delete.ISO=Please confirm that you want to delete this ISO. +message.action.delete.network=Please confirm that you want to delete this network. +message.action.delete.nexusVswitch=Please confirm that you want to delete this nexus 1000v +message.action.delete.physical.network=Please confirm that you want to delete this physical network +message.action.delete.pod=Please confirm that you want to delete this pod. +message.action.delete.primary.storage=Please confirm that you want to delete this primary storage. +message.action.delete.secondary.storage=Please confirm that you want to delete this secondary storage. +message.action.delete.security.group=Please confirm that you want to delete this security group. +message.action.delete.service.offering=Please confirm that you want to delete this service offering. +message.action.delete.snapshot=Please confirm that you want to delete this snapshot. +message.action.delete.system.service.offering=Please confirm that you want to delete this system service offering. +message.action.delete.template.for.all.zones=The template is used by all zones. Please confirm that you want to delete it from all zones. +message.action.delete.template=Please confirm that you want to delete this template. +message.action.delete.volume=Please confirm that you want to delete this volume. +message.action.delete.zone=Please confirm that you want to delete this zone. +message.action.destroy.instance=Please confirm that you want to destroy this instance. +message.action.destroy.systemvm=Please confirm that you want to destroy this System VM. +message.action.disable.cluster=Please confirm that you want to disable this cluster. +message.action.disable.nexusVswitch=Please confirm that you want to disable this nexus 1000v +message.action.disable.physical.network=Please confirm that you want to disable this physical network. +message.action.disable.pod=Please confirm that you want to disable this pod. +message.action.disable.static.NAT=Please confirm that you want to disable static NAT. +message.action.disable.zone=Please confirm that you want to disable this zone. +message.action.download.iso=Please confirm that you want to download this ISO. +message.action.download.template=Please confirm that you want to download this template. +message.action.enable.cluster=Please confirm that you want to enable this cluster. +message.action.enable.maintenance=Your host has been successfully prepared for maintenance. This process can take up to several minutes or longer depending on how many VMs are currently on this host. +message.action.enable.nexusVswitch=Please confirm that you want to enable this nexus 1000v +message.action.enable.physical.network=Please confirm that you want to enable this physical network. +message.action.enable.pod=Please confirm that you want to enable this pod. +message.action.enable.zone=Please confirm that you want to enable this zone. +message.action.expunge.instance=Please confirm that you want to expunge this instance. +message.action.force.reconnect=Your host has been successfully forced to reconnect. This process can take up to several minutes. +message.action.host.enable.maintenance.mode=Enabling maintenance mode will cause a live migration of all running instances on this host to any available host. +message.action.instance.reset.password=Please confirm that you want to change the ROOT password for this virtual machine. +message.action.manage.cluster=Please confirm that you want to manage the cluster. +message.action.primarystorage.enable.maintenance.mode=Warning\: placing the primary storage into maintenance mode will cause all VMs using volumes from it to be stopped. Do you want to continue? +message.action.reboot.instance=Please confirm that you want to reboot this instance. +message.action.reboot.router=All services provided by this virtual router will be interrupted. Please confirm that you want to reboot this router. +message.action.reboot.systemvm=Please confirm that you want to reboot this system VM. +message.action.release.ip=Please confirm that you want to release this IP. +message.action.remove.host=Please confirm that you want to remove this host. +message.action.reset.password.off=Your instance currently does not support this feature. +message.action.reset.password.warning=Your instance must be stopped before attempting to change its current password. +message.action.restore.instance=Please confirm that you want to restore this instance. +message.action.start.instance=Please confirm that you want to start this instance. +message.action.start.router=Please confirm that you want to start this router. +message.action.start.systemvm=Please confirm that you want to start this system VM. +message.action.stop.instance=Please confirm that you want to stop this instance. +message.action.stop.router=All services provided by this virtual router will be interrupted. Please confirm that you want to stop this router. +message.action.stop.systemvm=Please confirm that you want to stop this system VM. +message.action.take.snapshot=Please confirm that you want to take a snapshot of this volume. +message.action.revert.snapshot=Please confirm that you want to revert the owning volume to this snapshot. +message.action.unmanage.cluster=Please confirm that you want to unmanage the cluster. +message.action.vmsnapshot.delete=Please confirm that you want to delete this VM snapshot. +message.action.vmsnapshot.revert=Revert VM snapshot +message.activate.project=Are you sure you want to activate this project? +message.add.cluster.zone=Add a hypervisor managed cluster for zone +message.add.cluster=Add a hypervisor managed cluster for zone , pod +message.add.disk.offering=Please specify the following parameters to add a new disk offering +message.add.domain=Please specify the subdomain you want to create under this domain +message.add.firewall=Add a firewall to zone +message.add.guest.network=Please confirm that you would like to add a guest network +message.add.host=Please specify the following parameters to add a new host +message.add.ip.range.direct.network=Add an IP range to direct network in zone +message.add.ip.range.to.pod=

Add an IP range to pod\:

+message.add.ip.range=Add an IP range to public network in zone +message.add.load.balancer.under.ip=The load balancer rule has been added under IP\: +message.add.load.balancer=Add a load balancer to zone +message.add.network=Add a new network for zone\: +message.add.new.gateway.to.vpc=Please specify the information to add a new gateway to this VPC. +message.add.pod.during.zone.creation=Each zone must contain in one or more pods, and we will add the first pod now. A pod contains hosts and primary storage servers, which you will add in a later step. First, configure a range of reserved IP addresses for Cosmic\'s internal management traffic. The reserved IP range must be unique for each zone in the cloud. +message.add.pod=Add a new pod for zone +message.add.primary.storage=Add a new Primary Storage for zone , pod +message.add.primary=Please specify the following parameters to add a new primary storage +message.add.region=Please specify the required information to add a new region. +message.add.secondary.storage=Add a new storage for zone +message.add.service.offering=Please fill in the following data to add a new compute offering. +message.add.system.service.offering=Please fill in the following data to add a new system service offering. +message.add.template=Please enter the following data to create your new template +message.add.volume=Please fill in the following data to add a new volume. +message.add.VPN.gateway=Please confirm that you want to add a VPN Gateway +message.adding.host=Adding host +message.additional.networks.desc=Please select additional network(s) that your virtual instance will be connected to. +message.advanced.mode.desc=Choose this network model if you wish to enable VLAN support. This network model provides the most flexibility in allowing administrators to provide custom network offerings such as providing firewall, vpn, or load balancer support as well as enabling direct vs virtual networking. +message.advanced.security.group=Choose this if you wish to use security groups to provide guest VM isolation. +message.advanced.virtual=Choose this if you wish to use zone-wide VLANs to provide guest VM isolation. +message.after.enable.s3=S3-backed Secondary Storage configured. Note\: When you leave this page, you will not be able to re-configure S3 again. +message.after.enable.swift=Swift configured. Note\: When you leave this page, you will not be able to re-configure Swift again. +message.alert.state.detected=Alert state detected +message.allow.vpn.access=Please enter a username and password of the user that you want to allow VPN access. +message.apply.snapshot.policy=You have successfully updated your current snapshot policy. +message.attach.iso.confirm=Please confirm that you want to attach the ISO to this virtual instance. +message.attach.volume=Please fill in the following data to attach a new volume. If you are attaching a disk volume to a Windows based virtual machine, you will need to reboot the instance to see the attached disk. +message.basic.mode.desc=Choose this network model if you do *not* want to enable any VLAN support. All virtual instances created under this network model will be assigned an IP directly from the network and security groups are used to provide security and segregation. +message.change.ipaddress=Please confirm that you would like to change the IP address for this NIC on VM. +message.change.offering.confirm=Please confirm that you wish to change the service offering of this virtual instance. +message.change.password=Please change your password. +message.configure.all.traffic.types=You have multiple physical networks; please configure labels for each traffic type by clicking on the Edit button. +message.configuring.guest.traffic=Configuring guest traffic +message.configuring.physical.networks=Configuring physical networks +message.configuring.public.traffic=Configuring public traffic +message.configuring.storage.traffic=Configuring storage traffic +message.confirm.action.force.reconnect=Please confirm that you want to force reconnect this host. +message.confirm.delete.NuageVsp=Please confirm that you would like to delete Nuage Virtualized Services Directory +message.confirm.delete.SRX=Please confirm that you would like to delete SRX +message.confirm.delete.PA=Please confirm that you would like to delete Palo Alto +message.confirm.destroy.router=Please confirm that you would like to destroy this router +message.confirm.disable.provider=Please confirm that you would like to disable this provider +message.confirm.enable.provider=Please confirm that you would like to enable this provider +message.confirm.join.project=Please confirm you wish to join this project. +message.confirm.remove.IP.range=Please confirm that you would like to remove this IP range. +message.confirm.shutdown.provider=Please confirm that you would like to shutdown this provider +message.confirm.current.guest.CIDR.unchanged=Do you want to keep the current guest network CIDR unchanged? +message.confirm.delete.ciscoASA1000v=Please confirm you want to delete CiscoASA1000v +message.confirm.remove.selected.events=Please confirm you would like to remove the selected events +message.confirm.archive.selected.events=Please confirm you would like to archive the selected events +message.confirm.remove.event=Are you sure you want to remove this event? +message.confirm.archive.event=Please confirm that you want to archive this event. +message.confirm.remove.selected.alerts=Please confirm you would like to remove the selected alerts +message.confirm.archive.selected.alerts=Please confirm you would like to archive the selected alerts +message.confirm.delete.alert=Are you sure you want to delete this alert ? +message.confirm.archive.alert=Please confirm that you want to archive this alert. +message.confirm.migrate.volume=Do you want to migrate this volume? +message.confirm.attach.disk=Are you sure you want to attach disk? +message.confirm.create.volume=Are you sure you want to create volume? +message.confirm.enable.host=Please confirm that you want to enable the host +message.confirm.disable.host=Please confirm that you want to disable the host +message.confirm.delete.internal.lb=Please confirm you want to delete Internal LB +message.confirm.remove.load.balancer=Please confirm you want to remove VM from load balancer +message.confirm.delete.acl.list=Are you sure you want to delete this ACL list? +message.confirm.replace.acl.new.one=Do you want to replace the ACL with a new one? +message.copy.iso.confirm=Please confirm that you wish to copy your ISO to +message.copy.template=Copy template XXX from zone to +message.create.template.vm=Create VM from template +message.create.template.volume=Please specify the following information before creating a template of your disk volume\: . Creation of the template can range from several minutes to longer depending on the size of the volume. +message.create.template=Are you sure you want to create template? +message.creating.cluster=Creating cluster +message.creating.guest.network=Creating guest network +message.creating.physical.networks=Creating physical networks +message.creating.pod=Creating pod +message.creating.primary.storage=Creating primary storage +message.creating.secondary.storage=Creating secondary storage +message.creating.zone=Creating zone +message.decline.invitation=Are you sure you want to decline this project invitation? +message.dedicate.zone=Dedicating zone +message.delete.account=Please confirm that you want to delete this account. +message.delete.affinity.group=Please confirm that you would like to remove this anti-affinity group. +message.delete.gateway=Please confirm you want to delete the gateway +message.delete.project=Are you sure you want to delete this project? +message.delete.user=Please confirm that you would like to delete this user. +message.delete.VPN.connection=Please confirm that you want to delete VPN connection +message.delete.VPN.customer.gateway=Please confirm that you want to delete this VPN Customer Gateway +message.delete.VPN.gateway=Please confirm that you want to delete this VPN Gateway +message.desc.advanced.zone=For more sophisticated network topologies. This network model provides the most flexibility in defining guest networks and providing custom network offerings such as firewall, VPN, or load balancer support. +message.desc.basic.zone=Provide a single network where each VM instance is assigned an IP directly from the network. Guest isolation can be provided through layer-3 means such as security groups (IP address source filtering). +message.desc.cluster=Each pod must contain one or more clusters, and we will add the first cluster now. A cluster provides a way to group hosts. The hosts in a cluster all have identical hardware, run the same hypervisor, are on the same subnet, and access the same shared storage. Each cluster consists of one or more hosts and one or more primary storage servers. +message.desc.created.ssh.key.pair=Created a SSH Key Pair. +message.desc.host=Each cluster must contain at least one host (computer) for guest VMs to run on, and we will add the first host now. For a host to function in Cosmic, you must install hypervisor software on the host, assign an IP address to the host, and ensure the host is connected to the Cosmic management server.

Give the host\\'s DNS or IP address, the user name (usually root) and password, and any labels you use to categorize hosts. +message.desc.primary.storage=Each cluster must contain one or more primary storage servers, and we will add the first one now. Primary storage contains the disk volumes for all the VMs running on hosts in the cluster. Use any standards-compliant protocol that is supported by the underlying hypervisor. +message.desc.reset.ssh.key.pair=Please specify a ssh key pair that you would like to add to this VM. Please note the root password will be changed by this operation if password is enabled. +message.desc.secondary.storage=Each zone must have at least one NFS or secondary storage server, and we will add the first one now. Secondary storage stores VM templates, ISO images, and VM disk volume snapshots. This server must be available to all hosts in the zone.

Provide the IP address and exported path. +message.desc.zone=A zone is the largest organizational unit in Cosmic, and it typically corresponds to a single datacenter. Zones provide physical isolation and redundancy. A zone consists of one or more pods (each of which contains hosts and primary storage servers) and a secondary storage server which is shared by all pods in the zone. +message.detach.disk=Are you sure you want to detach this disk? +message.detach.iso.confirm=Please confirm that you want to detach the ISO from this virtual instance. +message.disable.account=Please confirm that you want to disable this account. By disabling the account, all users for this account will no longer have access to their cloud resources. All running virtual machines will be immediately shut down. +message.disable.snapshot.policy=You have successfully disabled your current snapshot policy. +message.disable.user=Please confirm that you would like to disable this user. +message.disable.vpn.access=Please confirm that you want to disable Remote Access VPN. +message.disable.vpn=Are you sure you want to disable VPN? +message.download.ISO=Please click 00000 to download ISO +message.download.template=Please click 00000 to download template +message.download.volume.confirm=Please confirm that you want to download this volume. +message.download.volume=Please click 00000 to download volume +message.edit.account=Edit ("-1" indicates no limit to the amount of resources create) +message.edit.confirm=Please confirm your changes before clicking "Save". +message.edit.limits=Please specify limits to the following resources. A "-1" indicates no limit to the amount of resources create. +message.edit.traffic.type=Please specify the traffic label you want associated with this traffic type. +message.enable.account=Please confirm that you want to enable this account. +message.enable.user=Please confirm that you would like to enable this user. +message.enable.vpn.access=VPN is currently disabled for this IP Address. Would you like to enable VPN access? +message.enable.vpn=Please confirm that you want Remote Access VPN enabled for this IP address. +message.enabled.vpn.ip.sec=Your IPSec pre-shared key is +message.enabled.vpn=Your Remote Access VPN is currently enabled and can be accessed via the IP +message.enabling.security.group.provider=Enabling Security Group provider +message.enabling.zone=Enabling zone +message.enter.token=Please enter the token that you were given in your invite e-mail. +message.generate.keys=Please confirm that you would like to generate new keys for this user. +message.guest.traffic.in.advanced.zone=Guest network traffic is communication between end-user virtual machines. Specify a range of VLAN IDs to carry guest traffic for each physical network. +message.guest.traffic.in.basic.zone=Guest network traffic is communication between end-user virtual machines. Specify a range of IP addresses that Cosmic can assign to guest VMs. Make sure this range does not overlap the reserved system IP range. +message.installWizard.click.retry=Click the button to retry launch. +message.installWizard.copy.whatIsCloudStack=Cosmic&\#8482 is a software platform that pools computing resources to build public, private, and hybrid Infrastructure as a Service (IaaS) clouds. Cosmic&\#8482 manages the network, storage, and compute nodes that make up a cloud infrastructure. Use Cosmic&\#8482 to deploy, manage, and configure cloud computing environments.

Extending beyond individual virtual machine images running on commodity hardware, Cosmic&\#8482 provides a turnkey cloud infrastructure software stack for delivering virtual datacenters as a service - delivering all of the essential components to build, deploy, and manage multi-tier and multi-tenant cloud applications. Both open-source and Premium versions are available, with the open-source version offering nearly identical features. +message.installWizard.copy.whatIsACluster=A cluster provides a way to group hosts. The hosts in a cluster all have identical hardware, run the same hypervisor, are on the same subnet, and access the same shared storage. Virtual machine instances (VMs) can be live-migrated from one host to another within the same cluster, without interrupting service to the user. A cluster is the third-largest organizational unit within a Cosmic deployment. Clusters are contained within pods, and pods are contained within zones.

Cosmic allows multiple clusters in a cloud deployment, but for a Basic Installation, we only need one cluster. +message.installWizard.copy.whatIsAHost=A host is a single computer. Hosts provide the computing resources that run the guest virtual machines. Each host has hypervisor software installed on it to manage the guest VMs (except for bare metal hosts, which are a special case discussed in the Advanced Installation Guide). For example, a Linux KVM-enabled server, a Citrix XenServer server, and an ESXi server are hosts. In a Basic Installation, we use a single host running XenServer or KVM.

The host is the smallest organizational unit within a Cosmic deployment. Hosts are contained within clusters, clusters are contained within pods, and pods are contained within zones. +message.installWizard.copy.whatIsAPod=A pod often represents a single rack. Hosts in the same pod are in the same subnet.

A pod is the second-largest organizational unit within a Cosmic deployment. Pods are contained within zones. Each zone can contain one or more pods; in the Basic Installation, you will have just one pod in your zone. +message.installWizard.copy.whatIsAZone=A zone is the largest organizational unit within a Cosmic deployment. A zone typically corresponds to a single datacenter, although it is permissible to have multiple zones in a datacenter. The benefit of organizing infrastructure into zones is to provide physical isolation and redundancy. For example, each zone can have its own power supply and network uplink, and the zones can be widely separated geographically (though this is not required). +message.installWizard.copy.whatIsCosmic=Cosmic is a software platform that pools computing resources to build public, private, and hybrid Infrastructure as a Service (IaaS) clouds. Cosmic manages the network, storage, and compute nodes that make up a cloud infrastructure. Use Cosmic to deploy, manage, and configure cloud computing environments.

Extending beyond individual virtual machine images running on commodity hardware, Cosmic provides a turnkey cloud infrastructure software stack for delivering virtual datacenters as a service - delivering all of the essential components to build, deploy, and manage multi-tier and multi-tenant cloud applications. Both open-source and Premium versions are available, with the open-source version offering nearly identical features. +message.installWizard.copy.whatIsPrimaryStorage=A Cosmic cloud infrastructure makes use of two types of storage\: primary storage and secondary storage. Both of these can be iSCSI or NFS servers, or localdisk.

Primary storage is associated with a cluster, and it stores the disk volumes of each guest VM for all the VMs running on hosts in that cluster. The primary storage server is typically located close to the hosts. +message.installWizard.copy.whatIsSecondaryStorage=Secondary storage is associated with a zone, and it stores the following\: +message.installWizard.now.building=Now building your cloud... +message.installWizard.tooltip.addCluster.name=A name for the cluster. This can be text of your choosing and is not used by Cosmic. +message.installWizard.tooltip.addHost.hostname=The DNS name or IP address of the host. +message.installWizard.tooltip.addHost.password=This is the password for the user named above (from your XenServer install). +message.installWizard.tooltip.addHost.username=Usually root. +message.installWizard.tooltip.addPod.name=A name for the pod +message.installWizard.tooltip.addPod.reservedSystemEndIp=This is the IP range in the private network that the Cosmic uses to manage Secondary Storage VMs and Console Proxy VMs. These IP addresses are taken from the same subnet as computing servers. +message.installWizard.tooltip.addPod.reservedSystemGateway=The gateway for the hosts in that pod. +message.installWizard.tooltip.addPod.reservedSystemNetmask=The netmask in use on the subnet the guests will use. +message.installWizard.tooltip.addPod.reservedSystemStartIp=This is the IP range in the private network that the Cosmic uses to manage Secondary Storage VMs and Console Proxy VMs. These IP addresses are taken from the same subnet as computing servers. +message.installWizard.tooltip.addPrimaryStorage.name=The name for the storage device. +message.installWizard.tooltip.addPrimaryStorage.path=(for NFS) In NFS this is the exported path from the server. Path (for SharedMountPoint). With KVM this is the path on each host that is where this primary storage is mounted. For example, "/mnt/primary". +message.installWizard.tooltip.addPrimaryStorage.server=(for NFS, iSCSI, or PreSetup) The IP address or DNS name of the storage device. +message.installWizard.tooltip.addSecondaryStorage.nfsServer=The IP address of the NFS server hosting the secondary storage +message.installWizard.tooltip.addSecondaryStorage.path=The exported path, located on the server you specified above +message.installWizard.tooltip.addZone.dns1=These are DNS servers for use by guest VMs in the zone. These DNS servers will be accessed via the public network you will add later. The public IP addresses for the zone must have a route to the DNS server named here. +message.installWizard.tooltip.addZone.dns2=These are DNS servers for use by guest VMs in the zone. These DNS servers will be accessed via the public network you will add later. The public IP addresses for the zone must have a route to the DNS server named here. +message.installWizard.tooltip.addZone.internaldns1=These are DNS servers for use by system VMs in the zone. These DNS servers will be accessed via the private network interface of the System VMs. The private IP address you provide for the pods must have a route to the DNS server named here. +message.installWizard.tooltip.addZone.internaldns2=These are DNS servers for use by system VMs in the zone. These DNS servers will be accessed via the private network interface of the System VMs. The private IP address you provide for the pods must have a route to the DNS server named here. +message.installWizard.tooltip.addZone.name=A name for the zone +message.installWizard.tooltip.configureGuestTraffic.description=A description for your network +message.installWizard.tooltip.configureGuestTraffic.guestEndIp=The range of IP addresses that will be available for allocation to guests in this zone. If one NIC is used, these IPs should be in the same CIDR as the pod CIDR. +message.installWizard.tooltip.configureGuestTraffic.guestGateway=The gateway that the guests should use +message.installWizard.tooltip.configureGuestTraffic.guestNetmask=The netmask in use on the subnet that the guests should use +message.installWizard.tooltip.configureGuestTraffic.guestStartIp=The range of IP addresses that will be available for allocation to guests in this zone. If one NIC is used, these IPs should be in the same CIDR as the pod CIDR. +message.installWizard.tooltip.configureGuestTraffic.name=A name for your network +message.instanceWizard.noTemplates=You do not have any templates available; please add a compatible template, and re-launch the instance wizard. +message.ip.address.changed=Your IP addresses may have changed; would you like to refresh the listing? Note that in this case the details pane will close. +message.iso.desc=Disc image containing data or bootable media for OS +message.join.project=You have now joined a project. Please switch to Project view to see the project. +message.launch.vm.on.private.network=Do you wish to launch your instance on your own private dedicated network? +message.launch.zone=Zone is ready to launch; please proceed to the next step. +message.lock.account=Please confirm that you want to lock this account. By locking the account, all users for this account will no longer be able to manage their cloud resources. Existing resources can still be accessed. +message.migrate.instance.confirm=Please confirm the host you wish to migrate the virtual instance to. +message.migrate.instance.to.host=Please confirm that you want to migrate instance to another host. +message.migrate.instance.to.ps=Please confirm that you want to migrate instance to another primary storage. +message.migrate.router.confirm=Please confirm the host you wish to migrate the router to\: +message.migrate.systemvm.confirm=Please confirm the host you wish to migrate the system VM to\: +message.migrate.volume=Please confirm that you want to migrate volume to another primary storage. +message.new.user=Specify the following to add a new user to the account +message.no.network.support.configuration.not.true=You do not have any zone that has security group enabled. Thus, no additional network features. Please continue to step 5. +message.no.network.support=Your selected hypervisor, vSphere, does not have any additional network features. Please continue to step 5. +message.no.projects.adminOnly=You do not have any projects.
Please ask your administrator to create a new project. +message.no.projects=You do not have any projects.
Please create a new one from the projects section. +message.number.clusters=

\# of Clusters

+message.number.hosts=

\# of Hosts

+message.number.pods=

\# of Pods

+message.number.storage=

\# of Primary Storage Volumes

+message.number.zones=

\# of Zones

+message.pending.projects.1=You have pending project invitations\: +message.pending.projects.2=To view, please go to the projects section, then select invitations from the drop-down. +message.please.add.at.lease.one.traffic.range=Please add at least one traffic range. +message.please.proceed=Please proceed to the next step. +message.please.select.a.configuration.for.your.zone=Please select a configuration for your zone. +message.please.select.a.different.public.and.management.network.before.removing=Please select a different public and management network before removing +message.please.select.networks=Please select networks for your virtual machine. +message.please.wait.while.zone.is.being.created=Please wait while your zone is being created; this may take a while... +message.project.invite.sent=Invite sent to user; they will be added to the project once they accept the invitation +message.public.traffic.in.advanced.zone=Public traffic is generated when VMs in the cloud access the internet. Publicly-accessible IPs must be allocated for this purpose. End users can use the Cosmic UI to acquire these IPs to implement NAT between their guest network and their public network.

Provide at least one range of IP addresses for internet traffic. +message.public.traffic.in.basic.zone=Public traffic is generated when VMs in the cloud access the Internet or provide services to clients over the Internet. Publicly accessible IPs must be allocated for this purpose. When a instance is created, an IP from this set of Public IPs will be allocated to the instance in addition to the guest IP address. Static 1-1 NAT will be set up automatically between the public IP and the guest IP. End users can also use the Cosmic UI to acquire additional IPs to implement static NAT between their instances and the public IP. +message.redirecting.region=Redirecting to region... +message.remove.region=Are you sure you want to remove this region from this management server? +message.remove.vpc=Please confirm that you want to remove the VPC +message.remove.vpn.access=Please confirm that you want to remove VPN access from the following user. +message.reset.password.warning.notPasswordEnabled=The template of this instance was created without password enabled +message.reset.password.warning.notStopped=Your instance must be stopped before attempting to change its current password +message.reset.VPN.connection=Please confirm that you want to reset VPN connection +message.restart.mgmt.server=Please restart your management server(s) for your new settings to take effect. +message.restart.mgmt.usage.server=Please restart your management server(s) and usage server(s) for your new settings to take effect. +message.restart.network=All services provided by this network will be interrupted. Please confirm that you want to restart this network. +message.restart.vpc=Please confirm that you want to restart the VPC +message.security.group.usage=(Use Ctrl-click to select all applicable security groups) +message.select.a.zone=A zone typically corresponds to a single datacenter. Multiple zones help make the cloud more reliable by providing physical isolation and redundancy. +message.select.instance=Please select an instance. +message.select.iso=Please select an ISO for your new virtual instance. +message.select.item=Please select an item. +message.select.security.groups=Please select security group(s) for your new VM +message.select.template=Please select a template for your new virtual instance. +message.setup.physical.network.during.zone.creation.basic=When adding a basic zone, you can set up one physical network, which corresponds to a NIC on the hypervisor. The network carries several types of traffic.

You may also drag and drop other traffic types onto the physical network. +message.setup.physical.network.during.zone.creation=When adding an advanced zone, you need to set up one or more physical networks. Each network corresponds to a NIC on the hypervisor. Each physical network can carry one or more types of traffic, with certain restrictions on how they may be combined.

Drag and drop one or more traffic types onto each physical network. +message.setup.successful=Cloud setup successful\! +message.snapshot.schedule=You can set up recurring snapshot schedules by selecting from the available options below and applying your policy preference +message.specify.url=Please specify URL +message.step.1.continue=Please select a template or ISO to continue +message.step.1.desc=Please select a template for your new virtual instance. You can also choose to select a blank template from which an ISO image can be installed onto. +message.step.2.continue=Please select a service offering to continue +message.step.3.continue=Please select a disk offering to continue +message.step.4.continue=Please select at least one network to continue +message.step.4.desc=Please select the primary network that your virtual instance will be connected to. +message.storage.traffic=Traffic between Cosmic\\'s internal resources, including any components that communicate with the Management Server, such as hosts and Cosmic system VMs. Please configure storage traffic here. +message.suspend.project=Are you sure you want to suspend this project? +message.template.desc=OS image that can be used to boot VMs +message.tooltip.dns.1=Name of a DNS server for use by VMs in the zone. The public IP addresses for the zone must have a route to this server. +message.tooltip.dns.2=A second DNS server name for use by VMs in the zone. The public IP addresses for the zone must have a route to this server. +message.tooltip.internal.dns.1=Name of a DNS server for use by Cosmic internal system VMs in the zone. The private IP address for the pods must have a route to this server. +message.tooltip.internal.dns.2=Name of a DNS server for use by Cosmic internal system VMs in the zone. The private IP address for the pods must have a route to this server. +message.tooltip.network.domain=A DNS suffix that will create a custom domain name for the network that is accessed by guest VMs. +message.tooltip.pod.name=A name for this pod. +message.tooltip.reserved.system.gateway=The gateway for the hosts in the pod. +message.tooltip.reserved.system.netmask=The network prefix that defines the pod subnet. Uses CIDR notation. +message.tooltip.zone.name=A name for the zone. +message.update.os.preference=Please choose a OS preference for this host. All virtual instances with similar preferences will be first allocated to this host before choosing another. +message.update.resource.count=Please confirm that you want to update resource counts for this account. +message.update.ssl=Please submit a new X.509 compliant SSL certificate chain to be updated to each console proxy and secondary storage virtual instance\: +message.update.ssl.succeeded=Update SSL Certificates succeeded +message.update.ssl.failed=Failed to update SSL Certificate. +message.validate.instance.name=Instance name can not be longer than 63 characters. Only ASCII letters a~z, A~Z, digits 0~9, hyphen are allowed. Must start with a letter and end with a letter or a digit. +message.virtual.network.desc=A dedicated virtualized network for your account. The broadcast domain is contained within a VLAN and all public network access is routed out by a virtual router. +message.vm.create.template.confirm=Create Template will reboot the VM automatically. +message.vm.review.launch=Please review the following information and confirm that your virtual instance is correct before launch. +message.volume.create.template.confirm=Please confirm that you wish to create a template for this disk volume. Creation of the template can range from several minutes to longer depending on the size of the volume. +message.you.must.have.at.least.one.physical.network=You must have at least one physical network +message.zone.creation.complete.would.you.like.to.enable.this.zone=Zone creation complete. Would you like to enable this zone? +message.Zone.creation.complete=Zone creation complete +message.zone.no.network.selection=The zone you selected does not have any choices for network selection. +message.zone.step.1.desc=Please select a network model for your zone. +message.zone.step.2.desc=Please enter the following info to add a new zone +message.zone.step.3.desc=Please enter the following info to add a new pod +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 shared primary storage, shared 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 primary storage, system.vm.use.local.storage needs to be set to true before you enable the zone.


Would you like to continue? +message.validate.fieldrequired=This field is required. +message.validate.fixfield=Please fix this field. +message.validate.email.address=Please enter a valid email address. +message.validate.URL=Please enter a valid URL. +message.validate.date=Please enter a valid date. +message.validate.date.ISO=Please enter a valid date (ISO). +message.validate.number=Please enter a valid number. +message.validate.digits=Please enter only digits. +message.validate.creditcard=Please enter a valid credit card number. +message.validate.equalto=Please enter the same value again. +message.validate.accept=Please enter a value with a valid extension. +message.validate.maxlength=Please enter no more than {0} characters. +message.validate.minlength=Please enter at least {0} characters. +message.validate.range.length=Please enter a value between {0} and {1} characters long. +message.validate.range=Please enter a value between {0} and {1}. +message.validate.max=Please enter a value less than or equal to {0}. +messgae.validate.min=Please enter a value greater than or equal to {0}. +message.creating.systemVM=Creating system VMs (this may take a while) +message.enabling.zone.dots=Enabling zone... +message.restoreVM=Do you want to restore the VM ? +message.no.host.available=No Hosts are available for Migration +message.network.addVM.desc=Please specify the network that you would like to add this VM to. A new NIC will be added for this network. +message.network.addVMNIC=Please confirm that you would like to add a new VM NIC for this network. +message.set.default.NIC=Please confirm that you would like to make this NIC the default for this VM. +message.set.default.NIC.manual=Please manually update the default NIC on the VM now. +message.instance.scaled.up.confirm=Do you really want to scale Up your instance ? +message.copy.template.confirm=Are you sure you want to copy template? +message.template.copying=Template is being copied. +message.XSTools61plus.update.failed=Failed to update Original XS Version is 6.1\+ field. Error\: +message.gslb.delete.confirm=Please confirm you want to delete this GSLB +message.portable.ip.delete.confirm=Please confirm you want to delete Portable IP Range +message.gslb.lb.remove.confirm=Please confirm you want to remove load balancing from GSLB +message.admin.guide.read=For VMware-based VMs, please read the dynamic scaling section in the admin guide before scaling. Would you like to continue?\, +message.tier.required=Tier is required +message.remove.ldap=Are you sure you want to delete the LDAP configuration? +message.action.downloading.template=Downloading template. +message.configure.ldap=Please confirm you would like to configure LDAP. +message.confirm.delete.ciscovnmc.resource=Please confirm you want to delete CiscoVNMC resource +message.confirm.add.vnmc.provider=Please confirm you would like to add the VNMC provider. +message.confirm.enable.vnmc.provider=Please confirm you would like to enable the VNMC provider. +message.confirm.disable.vnmc.provider=Please confirm you would like to disable the VNMC provider. +message.vnmc.available.list=VNMC is not available from provider list. +message.vnmc.not.available.list=VNMC is not available from provider list. +message.confirm.release.dedicate.vlan.range=Please confirm you want to release dedicated VLAN range +message.confirm.start.lb.vm=Please confirm you want to start LB VM +message.confirm.stop.lb.vm=Please confirm you want to stop LB VM +message.confirm.dedicate.zone=Do you really want to dedicate this zone to a domain/account? +message.confirm.release.dedicated.zone=Do you want to release this dedicated zone ? +message.dedicated.zone.released=Zone dedication released +message.read.admin.guide.scaling.up=Please read the dynamic scaling section in the admin guide before scaling up. +message.confirm.scale.up.system.vm=Do you really want to scale up the system VM ? +message.confirm.upgrade.router.newer.template=Please confirm that you want to upgrade router to use newer template +message.confirm.scale.up.router.vm=Do you really want to scale up the Router VM ? +message.confirm.upgrade.routers.newtemplate=Please confirm that you want to upgrade all routers in this zone to use newer template +message.confirm.upgrade.routers.pod.newtemplate=Please confirm that you want to upgrade all routers in this pod to use newer template +message.confirm.upgrade.routers.cluster.newtemplate=Please confirm that you want to upgrade all routers in this cluster to use newer template +message.confirm.upgrade.routers.account.newtemplate=Please confirm that you want to upgrade all routers in this account to use newer template +message.confirm.dedicate.pod.domain.account=Do you really want to dedicate this pod to a domain/account? +message.confirm.release.dedicated.pod=Do you want to release this dedicated pod ? +message.pod.dedication.released=Pod dedication released +message.confirm.dedicate.cluster.domain.account=Do you really want to dedicate this cluster to a domain/account? +message.cluster.dedicated=Cluster Dedicated +message.confirm.release.dedicated.cluster=Do you want to release this dedicated cluster ? +message.cluster.dedication.released=Cluster dedication released +message.confirm.dedicate.host.domain.account=Do you really want to dedicate this host to a domain/account? +message.host.dedicated=Host Dedicated +message.confirm.release.dedicated.host=Do you want to release this dedicated host ? +message.host.dedication.released=Host dedication released +message.confirm.refresh.blades=Please confirm that you want to refresh blades. +message.confirm.delete.secondary.staging.store=Please confirm you want to delete Secondary Staging Store. +message.select.tier=Please select a tier +message.disallowed.characters=Disallowed characters: \<\,\> +message.waiting.for.builtin.templates.to.load=Waiting for builtin templates to load... +message.systems.vms.ready=System VMs ready. +message.your.cloudstack.is.ready=Your Cosmic is ready\! +message.specifiy.tag.key.value=Please specify a tag key and value +message.enter.seperated.list.multiple.cidrs=Please enter a comma separated list of CIDRs if more than one +message.disabling.network.offering=Disabling network offering +message.confirm.enable.network.offering=Are you sure you want to enable this network offering? +message.enabling.network.offering=Enabling network offering +message.confirm.remove.network.offering=Are you sure you want to remove this network offering? +message.confirm.disable.network.offering=Are you sure you want to disable this network offering? +message.disabling.vpc.offering=Disabling VPC offering +message.confirm.enable.vpc.offering=Are you sure you want to enable this VPC offering? +message.enabling.vpc.offering=Enabling VPC offering +message.confirm.remove.vpc.offering=Are you sure you want to remove this VPC offering? +message.confirm.disable.vpc.offering=Are you sure you want to disable this VPC offering? +mode=Mode +network.rate=Network Rate +notification.reboot.instance=Reboot instance +notification.start.instance=Start instance +notification.stop.instance=Stop instance +side.by.side=Side by Side +state.Accepted=Accepted +state.Active=Active +state.Allocated=Allocated +state.Allocating=Allocating +state.BackedUp=Backed Up +state.BackingUp=Backing Up +state.Completed=Completed +state.Creating=Creating +state.Declined=Declined +state.Destroyed=Destroyed +state.detached=Detached +state.Disabled=Disabled +state.Enabled=Enabled +state.Error=Error +state.Expunging=Expunging +state.Migrating=Migrating +state.Pending=Pending +state.Ready=Ready +state.Running=Running +state.Starting=Starting +state.Stopped=Stopped +state.Stopping=Stopping +state.Suspended=Suspended +ui.listView.filters.all=All +ui.listView.filters.mine=Mine +label.na=N/A +label.nexthop=Nexthop +label.added.network.offering=Added network offering +hint.type.part.storage.tag=Type in part of a storage tag +hint.type.part.host.tag=Type in part of a host tag +hint.no.storage.tags=No storage tags found +hint.no.host.tags=No host tags found +label.availabilityZone=availabilityZone +label.diskoffering=diskoffering +title.upload.volume=Upload Volume +label.format.lower=format +label.checksum=checksum +label.assign.vms=Assign VMs +label.extractable.lower=extractable +label.region.details=Region details +message.added.new.nuage.vsp.controller=Added new Nuage Vsp Controller +message.added.vpc.offering=Added VPC offering +label.keyboard.language=Keyboard language +label.standard.us.keyboard=Standard (US) keyboard +label.uk.keyboard=UK keyboard +label.japanese.keyboard=Japanese keyboard +label.simplified.chinese.keyboard=Simplified Chinese keyboard +label.display.name=Display Name +label.zone.name=Zone Name +label.instances=Instances +label.event=Event +label.minutes.past.hour=minutes(s) past the hour +label.time.colon=Time: +label.min.past.the.hr=min past the hr +label.timezone.colon=Timezone: +label.keep.colon=Keep: +label.every=Every +label.day=Day +label.of.month=of month +label.add.private.gateway=Add Private Gateway +label.link.domain.to.ldap=Link Domain to LDAP +message.link.domain.to.ldap=Enable autosync for this domain in LDAP +label.ldap.link.type=Type +label.account.type=Account Type +message.desc.created.ssh.key.pair=Created a SSH Key Pair. +message.please.confirm.remove.ssh.key.pair=Please confirm that you want to remove this SSH Key Pair +message.password.has.been.reset.to=Password has been reset to +message.password.of.the.vm.has.been.reset.to=Password of the VM has been reset to +message.question.are.you.sure.you.want.to.add=Are you sure you want to add +label.domain.details=Domain details +label.account.details=Account details +label.user.details=User details +label.service.offering.details=Service offering details +label.system.service.offering.details=System service offering details +label.disk.offering.details=Disk offering details +label.network.offering.details=Network offering details +label.remove.this.physical.network=Remove this physical network +label.physical.network.name=Physical network name +label.save.changes=Save changes +label.autoscale.configuration.wizard=AutoScale Configuration Wizard +label.health.check.wizard=Health Check Wizard +label.health.check.message.desc=Your load balancer will automatically perform health checks on your Cosmic instances and only route traffic to instances that pass the health check +label.health.check.configurations.options=Configuration Options: +label.health.check.advanced.options=Advanced Options: +label.add.isolated.guest.network.with.sourcenat=Add Isolated Guest Network with SourceNat +message.network.remote.access.vpn.configuration=Remote Access VPN configuration has been generated, but it failed to apply. Please check connectivity of the network element, then re-try. +label.vpc.router.details=VPC Router Details +label.edit.rule=Edit rule +label.advanced.search=Advanced Search +label.internal.lb=Internal LB +label.public.lb=Public LB +label.acl.list.rules=ACL List Rules +label.static.routes=Static Routes +label.network.details=Network Details +label.scaleup.policy=ScaleUp Policy +label.scaledown.policy=ScaleDown Policy +label.configure.sticky.policy=Configure Sticky Policy +label.please.complete.the.following.fields=Please complete the following fields +message.desc.add.new.lb.sticky.rule=Add new LB sticky rule +label.ssh.key.pairs=SSH Key Pairs +message.desc.create.ssh.key.pair=Please fill in the following data to create or register a ssh key pair.

(1) If public key is set, Cosmic will register the public key. You can use it through your private key.

(2) If public key is not set, Cosmic will create a new SSH Key pair. In this case, please copy and save the private key. Cosmic will not keep it.
+message.removed.ssh.key.pair=Removed a SSH Key Pair +message.please.select.ssh.key.pair.use.with.this.vm=Please select a ssh key pair you want this VM to use: +message.configure.firewall.rules.allow.traffic=Configure the rules to allow Traffic +message.configure.firewall.rules.block.traffic=Configure the rules to block Traffic +message.ldap.group.import=All The users from the given group name will be imported +label.vpn.force.encapsulation=Force UDP Encapsulation of ESP Packets diff --git a/cosmic-client/WEB-INF/web.xml b/cosmic-client/WEB-INF/web.xml new file mode 100644 index 0000000000..506eaadf5e --- /dev/null +++ b/cosmic-client/WEB-INF/web.xml @@ -0,0 +1,86 @@ + + + + + + log4jConfigLocation + classpath:log4j-cloud.xml + + + org.springframework.web.util.Log4jConfigListener + + + + org.apache.cloudstack.spring.module.web.CloudStackContextLoaderListener + + + contextConfigLocation + classpath:META-INF/cosmic/webApplicationContext.xml + + + + cloudStartupServlet + com.cloud.servlet.CloudStartupServlet + 4 + + + + apiServlet + com.cloud.api.ApiServlet + 5 + + + + consoleServlet + com.cloud.servlet.ConsoleProxyServlet + 6 + + + + + staticResources + com.cloud.servlet.StaticResourceServlet + + + + apiServlet + /api/* + + + + consoleServlet + /console + + + + + staticResources + *.css + *.html + *.js + + + + java.lang.Exception + /error.jsp + + + diff --git a/cosmic-client/bindir/cloud-setup-management.in b/cosmic-client/bindir/cloud-setup-management.in new file mode 100755 index 0000000000..de76007fc8 --- /dev/null +++ b/cosmic-client/bindir/cloud-setup-management.in @@ -0,0 +1,61 @@ +#!/usr/bin/python +# 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. + +import sys +from cloudutils.syscfg import sysConfigFactory +from cloudutils.utilities import initLoging, UnknownSystemException +from cloudutils.cloudException import CloudRuntimeException, CloudInternalException +from cloudutils.globalEnv import globalEnv +from cloudutils.serviceConfigServer import cloudManagementConfig +from optparse import OptionParser +if __name__ == '__main__': + initLoging("@MSLOGDIR@/setupManagement.log") + glbEnv = globalEnv() + + parser = OptionParser() + parser.add_option("--https", action="store_true", dest="https", help="Enable HTTPs connection of management server") + parser.add_option("--tomcat7", action="store_true", dest="tomcat7", help="Use Tomcat7 configuration files in Management Server") + parser.add_option("--no-start", action="store_true", dest="nostart", help="Do not start management server after successful configuration") + (options, args) = parser.parse_args() + if options.https: + glbEnv.svrMode = "HttpsServer" + if options.tomcat7: + glbEnv.svrConf = "Tomcat7" + if options.nostart: + glbEnv.noStart = True + + glbEnv.mode = "Server" + + print "Starting to configure CloudStack Management Server:" + try: + syscfg = sysConfigFactory.getSysConfigFactory(glbEnv) + except UnknownSystemException: + print >>sys.stderr, ("Error: CloudStack failed to detect your " + "operating system. Exiting.") + sys.exit(1) + try: + syscfg.registerService(cloudManagementConfig) + syscfg.config() + print "CloudStack Management Server setup is Done!" + except (CloudRuntimeException, CloudInternalException), e: + print e + print "Try to restore your system:" + try: + syscfg.restore() + except: + pass diff --git a/cosmic-client/bindir/cloud-update-xenserver-licenses.in b/cosmic-client/bindir/cloud-update-xenserver-licenses.in new file mode 100755 index 0000000000..c64bc8f7c0 --- /dev/null +++ b/cosmic-client/bindir/cloud-update-xenserver-licenses.in @@ -0,0 +1,182 @@ +#!/usr/bin/python -W ignore::DeprecationWarning +# -*- 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. + + +import os +import sys +import glob +from random import choice +import string +from optparse import OptionParser +import MySQLdb +import paramiko +from threading import Thread + +# ---- This snippet of code adds the sources path and the waf configured PYTHONDIR to the Python path ---- +# ---- We do this so cloud_utils can be looked up in the following order: +# ---- 1) Sources directory +# ---- 2) waf configured PYTHONDIR +# ---- 3) System Python path +for pythonpath in ( + "@PYTHONDIR@", + os.path.join(os.path.dirname(__file__),os.path.pardir,os.path.pardir,"python","lib"), + ): + if os.path.isdir(pythonpath): sys.path.insert(0,pythonpath) +# ---- End snippet of code ---- +from cloud_utils import check_call, CalledProcessError, read_properties + +cfg = "@MSCONF@/db.properties" + +#---------------------- option parsing and command line checks ------------------------ + + +usage = """%prog <-a | host names / IP addresses...> + +This command deploys the license file specified in the command line into a specific XenServer host or all XenServer hosts known to the management server.""" + +parser = OptionParser(usage=usage) +parser.add_option("-a", "--all", action="store_true", dest="all", default=False, + help="deploy to all known hosts rather that a single host") + +#------------------ functions -------------------- + +def e(msg): parser.error(msg) + +def getknownhosts(host,username,password): + conn = MySQLdb.connect(host=host,user=username,passwd=password) + cur = conn.cursor() + cur.execute("SELECT h.private_ip_address,d.value FROM cloud.host h inner join cloud.host_details d on (h.id = d.host_id) where d.name = 'username' and setup = 1") + usernames = dict(cur.fetchall()) + cur.execute("SELECT h.private_ip_address,d.value FROM cloud.host h inner join cloud.host_details d on (h.id = d.host_id) where d.name = 'password' and setup = 1") + passwords = dict(cur.fetchall()) + creds = dict( [ [x,(usernames[x],passwords[x])] for x in usernames.keys() ] ) + cur.close() + conn.close() + return creds + +def splitlast(string,splitter): + splitted = string.split(splitter) + first,last = splitter.join(splitted[:-1]),splitted[-1] + return first,last + +def parseuserpwfromhosts(hosts): + creds = {} + for host in hosts: + user = "root" + password = "" + if "@" in host: + user,host = splitlast(host,"@") + if ":" in user: + user,password = splitlast(user,":") + creds[host] = (user,password) + return creds + +class XenServerConfigurator(Thread): + + def __init__(self,host,user,password,keyfiledata): + Thread.__init__(self) + self.host = host + self.user = user + self.password = password + self.keyfiledata = keyfiledata + self.retval = None # means all's good + self.stdout = "" + self.stderr = "" + self.state = 'initialized' + + def run(self): + try: + self.state = 'running' + c = paramiko.SSHClient() + c.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + c.connect(self.host,username=self.user,password=self.password) + sftp = c.open_sftp() + sftp.chdir("/tmp") + f = sftp.open("xen-license","w") + f.write(self.keyfiledata) + f.close() + sftp.close() + stdin,stdout,stderr = c.exec_command("xe host-license-add license-file=/tmp/xen-license") + c.exec_command("false") + self.stdout = stdout.read(-1) + self.stderr = stderr.read(-1) + self.retval = stdin.channel.recv_exit_status() + c.close() + if self.retval != 0: self.state = 'failed' + else: self.state = 'finished' + + except Exception,e: + self.state = 'failed' + self.retval = e + #raise + + def __str__(self): + if self.state == 'failed': + return "<%s XenServerConfigurator on %s@%s: %s>"%(self.state,self.user,self.host,str(self.retval)) + else: + return "<%s XenServerConfigurator on %s@%s>"%(self.state,self.user,self.host) + +#------------- actual code -------------------- + +(options, args) = parser.parse_args() + +try: + licensefile,args = args[0],args[1:] +except IndexError: e("The first argument must be the license file to use") + +if options.all: + if len(args) != 0: e("IP addresses cannot be specified if -a is specified") + config = read_properties(cfg) + creds = getknownhosts(config["db.cloud.host"],config["db.cloud.username"],config["db.cloud.password"]) + hosts = creds.keys() +else: + if not args: e("You must specify at least one IP address, or -a") + hosts = args + creds = parseuserpwfromhosts(hosts) + +try: + keyfiledata = file(licensefile).read(-1) +except OSError,e: + sys.stderr.write("The file %s cannot be opened"%licensefile) + sys.exit(1) + +configurators = [] +for host,(user,password) in creds.items(): + configurators.append ( XenServerConfigurator(host,user,password,keyfiledata ) ) + + +for c in configurators: c.start() + +for c in configurators: + print c.host + "...", + c.join() + if c.state == 'failed': + if c.retval: + msg = "failed with return code %s: %s%s"%(c.retval,c.stdout,c.stderr) + msg = msg.strip() + print msg + else: print "failed: %s"%c.retval + else: + print "done" + +successes = len( [ a for a in configurators if not a.state == 'failed' ] ) +failures = len( [ a for a in configurators if a.state == 'failed' ] ) + +print "%3s successes"%successes +print "%3s failures"%failures diff --git a/cosmic-client/cosmic-ui/LICENSE b/cosmic-client/cosmic-ui/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/cosmic-client/cosmic-ui/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/cosmic-client/cosmic-ui/README.md b/cosmic-client/cosmic-ui/README.md new file mode 100644 index 0000000000..8eb5e173d5 --- /dev/null +++ b/cosmic-client/cosmic-ui/README.md @@ -0,0 +1 @@ +# cosmic-ui diff --git a/cosmic-client/cosmic-ui/css/cloudstack3-ie7.css b/cosmic-client/cosmic-ui/css/cloudstack3-ie7.css new file mode 100644 index 0000000000..114e9c01ed --- /dev/null +++ b/cosmic-client/cosmic-ui/css/cloudstack3-ie7.css @@ -0,0 +1,209 @@ +/* +* 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. +*/ + +/*[fmt]1C20-1C0D-E*/ +div.panel div.list-view { + position: relative; + overflow-x: hidden; + margin-top: 0px; +} + +div.toolbar { + top: expression(this.offsetParent.scrollTop); +} + +div.toolbar div.text-search div.search-bar input { + float: left; + border: none; + margin-top: -1px; + margin-right: 0px; + margin-bottom: 0px; + margin-left: -12px; + width: 97%; + height: 67%; +} + +div.panel div.list-view div.fixed-header { + top: expression(this.offsetParent.scrollTop + 30); +} + +.detail-view div.list-view div.fixed-header { + top: 29px !important; + left: 12px !important; +} + +div.panel div.list-view div.data-table table.body { + top: 78px; +} + +.detail-view .main-groups { + width: 96%; + height: 416px; + position: relative; + overflow-x: hidden; + margin-top: -18px; +} + +.detail-view .ui-widget-content { + overflow-x: hidden !important; +} + +.tree-view { + position: relative; + top: expression(this.offsetParent.scrollTop); +} + +.tree-view li .name { + margin: 0px !important; +} + +.detail-view .button.done { + /*+placement:shift 0px 459px;*/ + position: relative; + left: 0px; + top: 459px; +} + +.detail-view ul { + /*+placement:shift;*/ + position: relative; + left: 0; + top: 0; +} + +.ui-tabs ul.ui-tabs-nav { + margin-top: 20px; +} + +.dashboard.admin .dashboard-container.sub.alerts ul { + top: expression(this.offsetParent.scrollTop); +} + +#user-options a:hover { + background: #ADADAD; + color: #FFFFFF; + /*+text-shadow:0px 1px 2px #383838;*/ + -moz-text-shadow: 0px 1px 2px #383838; + -webkit-text-shadow: 0px 1px 2px #383838; + -o-text-shadow: 0px 1px 2px #383838; + text-shadow: 0px 1px 2px #383838; +} + +.multi-wizard .progress ul li span.arrow { + display: none; + border-right: 0px solid #000000; +} + +.tree-view ul li .name { + float: right; +} + +.multi-edit .data .data-body .data-item .expandable-listing { + width: 99.8%; + border: 1px solid #CFC9C9; + max-height: 161px; + position: relative; + overflow: auto; + overflow-x: hidden; + top: expression(this.offsetParent.scrollTop); +} + +table tbody td, +table th { + padding: 9px 5px 8px 0px; + border-right: 1px solid #BFBFBF; + color: #495A76; + clear: none; + width: auto; + width: 88px; + min-width: 88px; + font-size: 11px; + overflow: hidden; + vertical-align: middle; +} + +.detail-view > .toolbar { + position: relative; + float: left; + left: -14px; +} + +.detail-view .list-view > .toolbar { + width: 100% !important; +} + +.system-chart ul.resources li .label { + /*+placement:shift 3px 9px;*/ + position: relative; + left: 3px; + top: 9px; + position: absolute; +} + +.system-chart ul.resources li .button.view-all .view-all-label { + /*+placement:shift 6px 5px;*/ + position: relative; + left: 6px; + top: 5px; +} + +.quick-view-tooltip div.title > span.title { +} + +.quick-view-tooltip div.title .icon { + top: -15px; +} + +.quick-view-tooltip table { +} + +.quick-view-tooltip .main-groups { + float: left; + left: 0px; + margin-top: 7px; +} + +.quick-view-tooltip .actions { + width: 426px !important; + float: left; +} + +.quick-view-tooltip .action { + float: left !important; + width: 54px !important; + margin-left: 7px !important; +} + +.quick-view-tooltip .action .label { + float: left; + max-width: 20px; +} + +.quick-view-tooltip .action .icon { + float: left; + height: 7px; + left: 0px; +} + +.list-view td.quick-view { +} + +.quick-view-tooltip .detail-view .detail-group.actions .action.text { + filter:alpha(opacity=100); +} diff --git a/cosmic-client/cosmic-ui/css/cloudstack3.css b/cosmic-client/cosmic-ui/css/cloudstack3.css new file mode 100644 index 0000000000..bea35c2026 --- /dev/null +++ b/cosmic-client/cosmic-ui/css/cloudstack3.css @@ -0,0 +1,13159 @@ +/*[fmt]1C20-1C0D-E*/ +/* +* 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. +*/ +/*+clearfix {*/ +div.toolbar:after, +.multi-wizard .progress ul li:after, +.multi-wizard.zone-wizard .select-container .field .select-array-item:after { + content: "."; + display: block; + height: 0; + clear: both; + visibility: hidden; + font-size: 0; +} + +div.toolbar, +.multi-wizard .progress ul li, +.multi-wizard.zone-wizard .select-container .field .select-array-item { + display: inline-block; +} + +div.toolbar, +.multi-wizard .progress ul li, +.multi-wizard.zone-wizard .select-container .field .select-array-item { + /*\*/ + display: block; + /**/ + -height: 1px; +} + +/*+}*/ +body { + min-width: 1224px; + font-family: sans-serif; + overflow: auto; + background: #EDE8E8; +} + +body.install-wizard { + font-family: sans-serif; + height: 769px !important; + overflow: auto; + overflow-x: hidden; + background: #FFFFFF url(../images/bg-login.png); +} + +#main-area { + width: 1224px; + height: 729px; + margin: auto; + border: 1px solid #D4D4D4; + /*+box-shadow:0px -5px 11px #B7B7B7;*/ + -moz-box-shadow: 0px -5px 11px #B7B7B7; + -webkit-box-shadow: 0px -5px 11px #B7B7B7; + -o-box-shadow: 0px -5px 11px #B7B7B7; + box-shadow: 0px -5px 11px #B7B7B7; + border: 1px solid #E8E8E8; +} + +#container { + /*[empty]width:;*/ + height: 100%; + margin: auto; + position: relative; +} + +#sections { + display: none; +} + +a { + color: #0B84DC; + text-decoration: none; +} + +a:hover { + text-decoration: underline; + color: #000000; +} + +/*Table*/ +table { + width: 940px; + max-width: 977px; + margin: 15px 15px 12px 12px; + font-size: 13px; + text-align: left; + text-indent: 10px; + border-bottom: 1px solid #C4C5C5; + border-collapse: collapse; + position: relative; +} + +table thead { + background: url(../images/bg-table-head.png) repeat-x; + cursor: default; +} + +table thead th { + border: 1px solid #C6C3C3; + color: #525252; + border-top: none; + border-bottom: 1px solid #CFC9C9; + text-align: left; + /*+text-shadow:0px 1px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px 1px #FFFFFF; + -o-text-shadow: 0px 1px 1px #FFFFFF; + text-shadow: 0px 1px 1px #FFFFFF; + font-weight: bold; + white-space: nowrap; +} + +table thead th.sorted { + color: #312F2F; + /*+text-shadow:0px 1px 1px #BFBFBF;*/ + -moz-text-shadow: 0px 1px 1px #BFBFBF; + -webkit-text-shadow: 0px 1px 1px #BFBFBF; + -o-text-shadow: 0px 1px 1px #BFBFBF; + text-shadow: 0px 1px 1px #BFBFBF; +} + +table thead th.sorted.desc { + background-position: 102% -111px; +} + +table thead th.sorted.asc { + background-position: 102% -157px; +} + +table tbody td, +table th { + padding: 10px 5px 6px; + border-right: 1px solid #BFBFBF; + color: #282828; + clear: none; + min-width: 88px; + font-size: 11px; + overflow: hidden; + vertical-align: middle; +} + +table tbody td.loading { + text-align: center; + background: #DBE2E9; + border-top: 1px solid #FBFBFB; +} + +table tbody td.truncated { + overflow: visible; + max-width: 120px; +} + +table tbody td.truncated > span { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.list-view-select table th.name, +.list-view-select table td.name { + width: 170px; + min-width: 170px; + max-width: 170px; +} + +/** Multiselect*/ +table thead th.multiselect, +table tbody td.multiselect { + width: 20px; + min-width: 20px; + max-width: 20px; +} + +table thead th.multiselect input, +table tbody td.multiselect input { + margin: 0; + /*+placement:shift -8px 0px;*/ + position: relative; + left: -8px; + top: 0px; +} + +table thead th.multiselect input { + margin-left: 2px; +} + +/** Actions table cell*/ +table tbody td.actions { + width: 130px; + max-width: 130px !important; + min-width: 130px !important; + vertical-align: middle; +} + +table tbody td.actions input { + /*+placement:shift 10px -6px;*/ + position: relative; + left: 10px; + top: -6px; + margin: 11px 0 0px; +} + +.list-view-select table tbody td.actions { + width: 40px !important; + min-width: 40px !important; + max-width: 40px !important; +} + +.list-view-select table tbody td.actions input { + margin: 0 0 0 -7px; +} + +.list-view-select table thead th.actions { + width: 40px !important; + min-width: 40px !important; + max-width: 40px !important; + text-indent: 5px; +} + +/** Quick view table cell*/ +table tbody td.quick-view, +table thead th.quick-view { + min-width: 58px; + max-width: 58px !important; + width: 58px !important; + height: 14px !important; + text-indent: 2px; + white-space: nowrap; +} + +table tbody td.quick-view { + cursor: pointer; +} + +table tbody td.quick-view .icon { + margin-left: 22px; + margin-top: 3px; + padding: 0px 0px 6px 12px; + background: url(../images/sprites.png) no-repeat -44px -62px; +} + +table tbody td.quick-view:hover .icon { + background-position: -44px -644px; +} + +table tbody tr.loading td.quick-view .icon { + display: none; +} + +table tbody tr.loading td.quick-view { + cursor: default; +} + +table tbody tr.loading td.quick-view .loading { + background-position: center center; +} + +/** Row styling*/ +table tbody tr { + border-left: 1px solid #C4C5C5; + border-right: 1px solid #C4C5C5; + border-top: 1px solid transparent; +} + +table tbody tr.even { + background: #FFFFFF; +} + +table tbody tr.odd { + background: #F2F0F0; +} + +table tbody tr.selected { + background: #CBDDF3; + border-top: 1px solid #EDF0F7 !important; + border-bottom: 1px solid #BABFD9; + text-shadow: 0px 1px 1px #FCFBF7; +} + +table tbody tr.to-remove { + background: #E05959; + border-top: 1px solid #EDF0F7 !important; + border-bottom: 1px solid #BABFD9; + text-shadow: 0px 1px 1px #FCFBF7; +} + +table tbody tr.loading { + background: #E2E9F0; +} + +table tbody tr.loading td { + /*+opacity:50%;*/ + filter: alpha(opacity=50); + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=50); + -moz-opacity: 0.5; + opacity: 0.5; +} + +table tbody tr.loading td.loading.icon { + background: url(../images/ajax-loader.gif) no-repeat center; + height: 35px; + padding: 0; +} + +table tbody tr div.loading { + display: block; + width: 50px; + height: 14px; + background: transparent url(../images/ajax-loader-small.gif) no-repeat center; + margin: auto; +} + +table th.resizable { + position: relative; + cursor: col-resize; +} + +table th div.ui-resizable-handle { + position: relative; + top: -30px; + float: right; +} + +/** Header, misc*/ +#template { + display: none; +} + +/*Login screen*/ +body.login { + background: url(../images/overlay-pattern.png) repeat center, #2c2c2c no-repeat center; + background-size: auto, cover; + overflow: hidden; +} + +.login { + display: block; + width: 100%; + height: 600px; + /*+placement:shift 0 80px;*/ + position: relative; + left: 0; + top: 80px; + background: #2c2c2c; + background-image: url(../images/sky.jpg); +} + +.login .select-language { + margin-top: 10px; + float: left; +} + +.login .select-language select { + width: 260px; + border: 1px solid #808080; + margin-top: 20px; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; + font-size: 12px; + /*+box-shadow:inset 0px 1px 1px #838383;*/ + -moz-box-shadow: inset 0px 1px 1px #838383; + -webkit-box-shadow: inset 0px 1px 1px #838383; + -o-box-shadow: inset 0px 1px 1px #838383; + box-shadow: inset 0px 1px 1px #838383; +} + +.login .fields { + width: 409px; + float: left; + margin: 72px 0 0 88px; +} + +.login .fields .field { + position: relative; +} + +.login .fields .field label { + font-size: 12px; + color: #4E4F53; + /*+placement:displace 9px 14px;*/ + position: absolute; + margin-left: 9px; + margin-top: 14px; +} + +.login .fields .field label.error { + color: #FF0000; + float: right; + left: 264px; + top: 0; +} + +.login .fields input { + width: 248px; + height: 20px; + margin: 5px 0 0; + text-indent: 1px; + font-size: 13px; + border: none; + padding: 5px; + /*+border-radius:3px;*/ + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + -khtml-border-radius: 3px; + border-radius: 3px; + /*+box-shadow:inset 0px 1px 1px #4E4E4E;*/ + -moz-box-shadow: inset 0px 1px 1px #4E4E4E; + -webkit-box-shadow: inset 0px 1px 1px #4E4E4E; + -o-box-shadow: inset 0px 1px 1px #4E4E4E; + box-shadow: inset 0px 1px 1px #4E4E4E; + background: #ECECEC; +} + +.login .fields input.error { + border-color: #FF8080; + background: #FFEAEA; +} + +.login .fields input[type=submit] { + background: transparent url(../images/sprites.png) -563px -747px; + cursor: pointer; + border: none; + margin: 7px 120px 0 -1px; + text-align: center; + width: 69px; + height: 25px; + display: block; + color: #FFFFFF; + font-weight: bold; + float: left; + text-indent: -1px; + /*+text-shadow:0px 1px 2px #000000;*/ + -moz-text-shadow: 0px 1px 2px #000000; + -webkit-text-shadow: 0px 1px 2px #000000; + -o-text-shadow: 0px 1px 2px #000000; + text-shadow: 0px 1px 2px #000000; +} + +.login .fields input[type=samlsubmit] { + background: transparent url(../images/sprites.png) -563px -747px; + cursor: pointer; + border: none; + text-align: center; + width: 60px; + height: 15px; + display: block; + color: #FFFFFF; + font-weight: bold; + text-indent: -1px; + /*+text-shadow:0px 1px 2px #000000;*/ + -moz-text-shadow: 0px 1px 2px #000000; + -webkit-text-shadow: 0px 1px 2px #000000; + -o-text-shadow: 0px 1px 2px #000000; + text-shadow: 0px 1px 2px #000000; + font-size: 10px; +} + +.login .fields input[type=submit]:hover { + background-position: -563px -772px; +} + +.login .logo { + width: 290px; + height: 40px; + float: left; + margin: 50px 0 0 0px; + background: url(../images/logo-cosmic.png) no-repeat 0 0; +} + +.login.nologo .logo { + background-image: url(../images/logo-cosmic.png); +} + +.login form { + display: block; + width: 500px; + height: 100%; + margin: auto; + background: #2c2c2c; +} + +/*About dialog*/ +.dialog-about .ui-widget-content { + padding-left: 0; + padding-right: 0; + width: 100% !important; +} + +.dialog-about .logo { + font-size: 26px; + color: #636363; + padding-top: 20px; +} + +.dialog-about .version { + font-size: 12px; + padding-top: 10px; +} + +.dialog-about .ui-button { + float: none; + margin: 0 auto; +} + +#browser div.panel div.detail-view .toolbar { + width: 100%; +} + +div.list-view table tbody td span { + display: block; + float: left; + max-width: 89%; + word-break: break-all; + word-wrap: break-word; + text-indent: 0; + margin-left: 12px; + line-height: 15px; +} + +div.list-view div.toolbar div.section-switcher div.section-select label { + margin: 0 9px 0 0; +} + +.loading-overlay { + position: absolute; + width: 100%; + height: 100%; + left: 0px; + top: 0px; + background: #F2F2F2 url(../images/ajax-loader.gif) no-repeat center; + z-index: 500; + /*+opacity:70%;*/ + filter: alpha(opacity=70); + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=70); + -moz-opacity: 0.7; + opacity: 0.7; +} + +.loading-overlay span { + display: block; + text-align: center; + margin: 155px 0 0 5px; + color: #4B4B4B; +} + +.detail-view .ui-tabs-panel .loading-overlay { + background-position: 50% 250px; +} + +/*Install wizard*/ +.install-wizard { + width: 1024px; + height: 768px; + margin: auto; + border-top: none; + position: relative; +} + +.install-wizard .header { + text-align: center; + background: url(../images/bg-login.png); + color: #626E82; + height: 365px; + padding: 32px 0 89px; + /*+text-shadow:0px 1px 2px #FFFFFF;*/ + -moz-text-shadow: 0px 1px 2px #FFFFFF; + -webkit-text-shadow: 0px 1px 2px #FFFFFF; + -o-text-shadow: 0px 1px 2px #FFFFFF; + text-shadow: 0px 1px 2px #FFFFFF; + z-index: 9; +} + +.install-wizard .header h3 { + font-size: 20px; +} + +.install-wizard .step { + max-width: 691px; + margin: auto; + padding: 56px 0 0; +} + +.install-wizard .step .title { + width: 303px; + margin: auto auto 30px; + font-size: 22px; + clear: both; + color: #626E82; +} + +.install-wizard .step .subtitle { + color: #4B5E69; + font-weight: bold; + font-size: 12px; +} + +.install-wizard .step p { + color: #4A4A4A; + font-size: 15px; + line-height: 23px; + background: url(../images/bg-gradient-white-transparent.png) repeat-x -114px -270px; +} + +.install-wizard .step ul li { + margin: 14px 0 0 18px; + width: 465px; + font-size: 13px; + list-style: disc; +} + +.install-wizard .step .field { + text-align: left; + margin: 0 0 12px; +} + +.install-wizard .step .field label { + display: block; + clear: both; + font-size: 11px; + color: #4D4D4D; +} + +.install-wizard .step .field label.error { + color: #FF2424; + font-size: 11px; +} + +.install-wizard .body { + width: 1012px; + height: 762px; + margin: -352px auto auto; + z-index: 10; + background: url(../images/bg-gradient-white-transparent.png) repeat-x -114px -141px; + /*+box-shadow:0px -3px 4px #CFCFCF;*/ + -moz-box-shadow: 0px -3px 4px #CFCFCF; + -webkit-box-shadow: 0px -3px 4px #CFCFCF; + -o-box-shadow: 0px -3px 4px #CFCFCF; + box-shadow: 0px -3px 4px #CFCFCF; +} + +.install-wizard h2 { + font-size: 28px; + margin: 0 0 19px; +} + +.install-wizard input[type=text], +.install-wizard input[type=password], +.install-wizard input[type=text], +.install-wizard select { + width: 288px; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; + border: 1px solid #CDCDCD; + /*+box-shadow:inset 0px 1px #AEAEAE;*/ + -moz-box-shadow: inset 0px 1px #AEAEAE; + -webkit-box-shadow: inset 0px 1px #AEAEAE; + -o-box-shadow: inset 0px 1px #AEAEAE; + box-shadow: inset 0px 1px #AEAEAE; + -moz-box-shadow: inset 0px 1px 0px #AEAEAE; + -webkit-box-shadow: inset 0px 1px 0px #AEAEAE; + -o-box-shadow: inset 0px 1px 0px #AEAEAE; + font-size: 14px; + color: #232323; + padding: 6px; + background: #F7F7F7; +} + +.install-wizard .button { + background: url(../images/bg-gradients.png) 0px -221px; + padding: 7px 16px 7px 18px; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; + border: 1px solid #505050; + color: #FFFFFF; + font-size: 12px; + font-weight: bold; + /*+text-shadow:0px -1px 3px #3F4351;*/ + -moz-text-shadow: 0px -1px 3px #3F4351; + -webkit-text-shadow: 0px -1px 3px #3F4351; + -o-text-shadow: 0px -1px 3px #3F4351; + text-shadow: 0px -1px 3px #3F4351; + float: right; + cursor: pointer; + margin-top: 15px; +} + +.install-wizard .button.advanced-installation, +.install-wizard .button.go-back { + background: #E0DEDE; + color: #3B3B3B; + border: 1px solid #C7C2C2; + /*+text-shadow:0px 0px #FFFFFF;*/ + -moz-text-shadow: 0px 0px #FFFFFF; + -webkit-text-shadow: 0px 0px #FFFFFF; + -o-text-shadow: 0px 0px #FFFFFF; + text-shadow: 0px 0px #FFFFFF; + float: left; +} + +.install-wizard .button.go-back { + padding: 9px 16px 10px 18px; + font-size: 12px; +} + +.install-wizard .setup-form .button.go-back { + /*+placement:shift 15px -14px;*/ + position: relative; + left: 15px; + top: -14px; +} + +.install-wizard .step { + z-index: 11; + position: relative; +} + +.install-wizard .step .tooltip-info { + /*+placement:shift 547px 50px;*/ + position: relative; + left: 547px; + top: 50px; + position: absolute; +} + +/*** Intro*/ +.install-wizard .step.intro.what-is-cloudstack p { + background: url(../images/bg-what-is-cloudstack.png) no-repeat 50% 237px; + height: 540px; +} + +/*** Diagram*/ +.install-wizard .diagram { + width: 910px; + height: 385px; + /*+placement:shift 65px 496px;*/ + position: relative; + left: 65px; + top: 496px; + position: absolute; + z-index: 10; +} + +.install-wizard .diagram .part { + background: url(../images/install-wizard-parts.png) no-repeat; + display: none; +} + +.install-wizard .diagram .part.zone { + width: 742px; + height: 135px; + background-position: -267px -580px; + /*+placement:shift 77px 222px;*/ + position: relative; + left: 77px; + top: 222px; + position: absolute; +} + +.install-wizard .diagram .part.loading { + width: 742px; + height: 432px; + background-position: -1264px -487px; + /*+placement:shift 105px -67px;*/ + position: relative; + left: 105px; + top: -67px; + position: absolute; +} + +.install-wizard .diagram .part.loading .icon { + width: 61px; + height: 76px; + background: url(../images/ajax-loader.gif) no-repeat; + /*+placement:shift 322px 130px;*/ + position: relative; + left: 322px; + top: 130px; +} + +.install-wizard .diagram .part.pod { + width: 266px; + height: 396px; + background-position: -47px -3px; + /*+placement:shift 313px -76px;*/ + position: relative; + left: 313px; + top: -76px; + position: absolute; +} + +.install-wizard .diagram .part.cluster { + width: 266px; + height: 396px; + background-position: -364px 1px; + /*+placement:shift 313px -76px;*/ + position: relative; + left: 313px; + top: -76px; + position: absolute; +} + +.install-wizard .diagram .part.host { + width: 266px; + height: 396px; + background-position: -688px 1px; + /*+placement:shift 313px -76px;*/ + position: relative; + left: 313px; + top: -76px; + position: absolute; +} + +.install-wizard .diagram .part.primaryStorage { + width: 275px; + height: 396px; + background-position: -1046px 1px; + /*+placement:shift 306px -76px;*/ + position: relative; + left: 306px; + top: -76px; + position: absolute; +} + +.install-wizard .diagram .part.secondaryStorage { + width: 385px; + height: 396px; + background-position: -1469px 1px; + /*+placement:shift 306px -76px;*/ + position: relative; + left: 306px; + top: -76px; + position: absolute; +} + +/*** Setup form*/ +.install-wizard .step .setup-form { + display: inline-block; + background: url(../images/bg-transparent-white.png); + width: 469px; + border: 1px solid #DFDFDF; + /*+text-shadow:0px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px #FFFFFF; + -o-text-shadow: 0px 1px #FFFFFF; + text-shadow: 0px 1px #FFFFFF; + -moz-text-shadow: 0px 1px 0px #FFFFFF; + -webkit-text-shadow: 0px 1px 0px #FFFFFF; + -o-text-shadow: 0px 1px 0px #FFFFFF; + /*+box-shadow:0px 5px 9px #9F9F9F;*/ + -moz-box-shadow: 0px 5px 9px #9F9F9F; + -webkit-box-shadow: 0px 5px 9px #9F9F9F; + -o-box-shadow: 0px 5px 9px #9F9F9F; + box-shadow: 0px 5px 9px #9F9F9F; +} + +.install-wizard .step .setup-form .title { + float: left; + margin: 17px 0 0 29px; + color: #626F7C; +} + +.install-wizard .step .setup-form .field { + width: 389px; + display: inline-block; + margin: 6px 0 1px 31px; + padding: 9px; + color: #57646D; +} + +.install-wizard .step .setup-form .field .name { + width: 98px; + text-align: right; + float: left; + font-size: 13px; + padding: 10px 0 0 0px; +} + +.install-wizard .step .setup-form .field .value { + float: right; +} + +.install-wizard .step .setup-form input[type=text], +.install-wizard .step .setup-form input[type=password] { + width: 278px; + border: 1px solid #8D8D8D; + padding: 2px 2px 1px; + margin: 6px 4px 0 0; +} + +.install-wizard .step .setup-form .range-item { + width: 142px; + float: left; +} + +.install-wizard .step .setup-form .range-item input { + width: 131px; +} + +.install-wizard .step .setup-form .multi-range input[type=text] { + width: 128px; +} + +.install-wizard .step .setup-form input.button { + margin: 0 30px 14px 15px; +} + +/*** Step: Change user*/ +.install-wizard .step.change-user { + text-align: center; + padding-top: 95px; + width: 316px; + margin: auto; +} + +.install-wizard .step.intro { +} + +.install-wizard .step.intro iframe { + width: 99%; + height: 99%; + margin: 4px; +} + +.install-wizard .step.intro .title { + color: #565454; + margin-left: 0; + margin-bottom: 21px; + font-size: 25px; +} + +.install-wizard .step.intro .subtitle { + margin-bottom: 9px; +} + +.install-wizard .step.intro .subtitle li { + background: url(../images/ajax-loader-small.gif) no-repeat 3px 0px; + position: relative; + width: 45%; + padding: 1px 0 1px 30px; + height: 24px; + list-style: none; +} + +.install-wizard .step.intro .subtitle li.complete { + background: url(../images/icons.png) -1px -224px; +} + +.install-wizard .step.intro .subtitle li.error { + background: url(../images/icons.png) -1px -190px; +} + +.install-wizard .step.intro .subtitle li img { + float: right; +} + +/*Notifications*/ +div.notification-box { + width: 323px; + height: 354px; + background: url(../images/bg-notifications.png) no-repeat 0px 0px; +} + +div.notification-box h3 { + color: #FFFFFF; + /*+placement:shift 0px 35px;*/ + position: relative; + left: 0px; + top: 35px; + text-align: center; + font-size: 21px; + letter-spacing: 1px; + /*+text-shadow:0px 1px 2px #000000;*/ + -moz-text-shadow: 0px 1px 2px #000000; + -webkit-text-shadow: 0px 1px 2px #000000; + -o-text-shadow: 0px 1px 2px #000000; + text-shadow: 0px 1px 2px #000000; +} + +div.notification-box .container { + background: #FFFFFF; + width: 296px; + height: 241px; + margin: auto; + /*+placement:shift 3px 46px;*/ + position: relative; + left: 3px; + top: 46px; + /*+box-shadow:inset 0px 3px 7px #656565;*/ + -moz-box-shadow: inset 0px 3px 7px #656565; + -webkit-box-shadow: inset 0px 3px 7px #656565; + -o-box-shadow: inset 0px 3px 7px #656565; + box-shadow: inset 0px 3px 7px #656565; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; + border: 1px solid #8198AE; +} + +div.notification-box .container ul { + margin-top: 8px; + height: 229px; + overflow: auto; + overflow-x: hidden; + width: 294px; +} + +div.notification-box .container ul li { + width: 100%; + height: 41px; + border-bottom: 1px solid #CECECE; + overflow-x: hidden; + text-indent: 0; + font-size: 12px; + color: #4D5E6E; + background: url(../images/icons.png) no-repeat 10px -213px; + cursor: pointer; +} + +div.notification-box .container ul li.error { + height: 53px; + background: url(../images/icons.png) no-repeat 10px -171px; +} + +div.notification-box .container ul li.error .subtitle { + display: block; + width: 213px; + height: 10px; + overflow: hidden; + white-space: nowrap; + float: left; + text-overflow: ellipsis; + color: #808080; + text-indent: 0; + padding: 0; + margin: 0; + /*+placement:shift 48px 17px;*/ + position: relative; + left: 48px; + top: 17px; +} + +div.notification-box .container ul li span { + float: left; + /*+placement:shift 48px 15px;*/ + position: relative; + left: 48px; + top: 15px; + max-width: 202px; + font-size: 14px; + font-weight: 100; + overflow: hidden; +} + +div.notification-box .container ul li span:hover { + color: #5FAAF7; + text-decoration: underline; +} + +div.notification-box .container ul div.remove { + width: 17px; + height: 21px; + background: url(../images/buttons.png) no-repeat -623px -8px; + float: right; + margin: -4px 8px 0px 0px; + cursor: pointer; + /*+placement:shift 0px 16px;*/ + position: relative; + left: 0px; + top: 16px; +} + +div.notification-box .container ul div.remove:hover { + background-position: -606px -8px; +} + +div.notification-box .container ul li.pending { + color: #7E96AC; + background: url(../images/ajax-loader.gif) no-repeat 8px 6px; +} + +div.notification-box .container ul li.first { + border-top: none; +} + +div.notification-box .container ul li.last { +} + +div.notification-box .button { + float: left; + background: url(../images/buttons.png) no-repeat; + cursor: pointer; + /*+placement:shift 0px 51px;*/ + position: relative; + left: 0px; + top: 51px; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; + border-bottom: 1px solid #2B2B2B; + /*+box-shadow:0px 0px 2px #272727;*/ + -moz-box-shadow: 0px 0px 2px #272727; + -webkit-box-shadow: 0px 0px 2px #272727; + -o-box-shadow: 0px 0px 2px #272727; + box-shadow: 0px 0px 2px #272727; + padding: 5px 10px 6px; +} + +div.notification-box .button span { + color: #FFFFFF; + font-size: 11px; + font-weight: bold; + letter-spacing: 1px; + /*+text-shadow:0px -1px 2px #171717;*/ + -moz-text-shadow: 0px -1px 2px #171717; + -webkit-text-shadow: 0px -1px 2px #171717; + -o-text-shadow: 0px -1px 2px #171717; + text-shadow: 0px -1px 2px #171717; +} + +div.notification-box .button.clear-list { + background: url(../images/gradients.png) 0px -10px; + margin-left: 16px; +} + +div.notification-box .button.clear-list:hover { + background-position: 0px -51px; +} + +div.notification-box .button.close { + background: url(../images/gradients.png) 0px -317px; + float: right; + margin-right: 10px; + border-bottom: 1px solid #232323; +} + +div.notification-box .button.close:hover { + background-position: -4px -368px; +} + +/*** Corner alert*/ +div.notification.corner-alert { + background: #FFFFFF; + background: rgba(255, 255, 255, 0.95); + /*+box-shadow:0px 2px 10px #000000;*/ + -moz-box-shadow: 0px 2px 10px #000000; + -webkit-box-shadow: 0px 2px 10px #000000; + -o-box-shadow: 0px 2px 10px #000000; + box-shadow: 0px 2px 10px #000000; + width: 300px; + height: 75px; + /*+border-radius:3px;*/ + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + -khtml-border-radius: 3px; + border-radius: 3px; + position: absolute; + text-indent: 10px; + padding: 7px 7px 0; + font-size: 12px; + /*+opacity:70%;*/ + filter: alpha(opacity=70); + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=70); + -moz-opacity: 0.7; + opacity: 0.7; + z-index: 100; + margin: 38px 0 0 -56px; +} + +div.notification.corner-alert .top-arrow { + background: url(../images/sprites.png) no-repeat -580px -1353px; + width: 36px; + height: 15px; + position: absolute; + top: -15px; + left: 50px; +} + +div.notification.corner-alert div.title { + width: 100%; + height: 33px; + color: #FFFFFF; +} + +div.notification.corner-alert div.title span { + /*+placement:shift 0px 10px;*/ + position: relative; + left: 0px; + top: 10px; + color: #6D6D6D; + padding: 3px 0 12px 24px; + font-weight: 100; + font-size: 14px; + padding-left: 33px; + background: url(../images/icons.png) no-repeat 3px -223px; +} + +div.notification.corner-alert.error div.title span { + background: url(../images/icons.png) no-repeat -2px -190px; +} + +div.notification.corner-alert div.message span { + position: relative; + padding-top: 6px; + font-size: 14px; + display: block; + color: #000000; + /*+placement:shift 17px -2px;*/ + position: relative; + left: 17px; + top: -2px; +} + +/*Tooltips*/ +.tooltip { +} + +.tooltip-info { + width: 239px; + min-height: 83px; + display: inline-block; + background: #FFFFFF; + border: 1px solid #BEB8B8; + position: absolute; + z-index: 1000; + /*+border-radius:22px;*/ + -moz-border-radius: 22px; + -webkit-border-radius: 22px; + -khtml-border-radius: 22px; + border-radius: 22px; + border-radius: 22px 22px 22px 22px; +} + +.tooltip-info .arrow { + width: 27px; + height: 47px; + position: absolute; + top: 17px; + left: -18px; + background: url(../images/sprites.png) -583px -939px; +} + +.tooltip-info .title { + color: #485766; + margin: 12px; + font-size: 19px; +} + +.tooltip-info .content { + width: 182px; + font-size: 11px; + line-height: 19px; + padding-bottom: 13px; + overflow: auto; + overflow-x: hidden; + margin: auto; +} + +/*List view*/ +div.panel div.list-view { + overflow: auto; + overflow-x: hidden; + height: 632px; + margin-top: 30px; +} + +.detail-view div.list-view { + width: 930px; + border: 1px solid #DAD4D4; + margin: 41px auto auto !important; + height: 536px !important; + background: #F7F7F7; +} + +div.panel div.list-view div.data-table table { + width: 955px; + margin-top: 44px; +} + +.detail-view div.list-view div.data-table table { + width: 903px !important; +} + +.detail-view div.list-view div.data-table table td { + border-left: 1px solid #CACACA; +} + +div.panel div.list-view div.fixed-header { + position: absolute; + top: 29px; + left: 12px; + width: 960px; + height: 47px; + display: table; + background-color: #F7F7F7; + margin: 0; + z-index: 1; +} + +.detail-view div.list-view div.fixed-header { + width: 903px !important; + top: 49px !important; + left: 29px !important; + background: #FFFFFF; +} + +.detail-view div#details-tab-zones div.fixed-header { + left: 25px !important; +} + +.detail-view div.list-view div.fixed-header table { + width: 100% !important; +} + +.project-view div.panel div.list-view div.fixed-header { + background: #6D747D; +} + +div.panel div.list-view div.fixed-header table { + margin: 0; + /*+placement:shift 0px 18px;*/ + position: relative; + left: 0px; + top: 18px; + width: 955px; + /*+box-shadow:0px 4px 10px #DFE1E3;*/ + -moz-box-shadow: 0px 4px 10px #DFE1E3; + -webkit-box-shadow: 0px 4px 10px #DFE1E3; + -o-box-shadow: 0px 4px 10px #DFE1E3; + box-shadow: 0px 4px 10px #DFE1E3; +} + +.project-view div.panel div.list-view div.fixed-header table { + /*+box-shadow:0px 2px 2px #CACDD1;*/ + -moz-box-shadow: 0px 2px 2px #CACDD1; + -webkit-box-shadow: 0px 2px 2px #CACDD1; + -o-box-shadow: 0px 2px 2px #CACDD1; + box-shadow: 0px 2px 2px #CACDD1; +} + +div.list-view td.state { + width: 120px; + min-width: 120px; + max-width: 120px; +} + +div.list-view td.first { + cursor: pointer; +} + +div.list-view tr:not(.multi-edit-selected) td.first:hover { + color: #3A82CD; +} + +div.list-view td.state span { + padding: 1px 0 0 18px; + text-align: center; + width: 80px; + /*+text-shadow:0px 1px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px 1px #FFFFFF; + -o-text-shadow: 0px 1px 1px #FFFFFF; + text-shadow: 0px 1px 1px #FFFFFF; + background: url(../images/sprites.png) 1px -526px; +} + +div.list-view td.state.on span { + background-image: url(../images/sprites.png); + background-repeat: no-repeat; + color: #008000; + background-position: 1px -460px; +} + +div.list-view td.state.off span { + background-image: url(../images/sprites.png); + background-repeat: no-repeat; + color: #B90606; + background-position: 1px -492px; +} + +div.list-view td.state.warning span { + background-image: url(../images/sprites.png); + background-repeat: no-repeat; + color: #B90606; + background-position: 1px -558px; +} + +div.list-view td.state.transition span { + background-image: url(../images/sprites.png); + background-repeat: no-repeat; + color: #B90606; + background-position: 1px -432px; +} + +.horizontal-overflow tbody td, .horizontal-overflow thead th { + min-width: 40px; + padding: 10px 10px 5px 0px; +} + +.horizontal-overflow th.quick-view { + padding-left: 5px; +} + +.groupable-header { + background: url(../images/bg-table-head.png); + border-left: 1px solid #C6C3C3; + border-right: 1px solid #C6C3C3; +} + +.groupable-header-columns th { + border: none; +} + +table.horizontal-overflow td.state { + width: 55px; + min-width: 55px; + max-width: 55px; +} + +table.no-split td.first { + min-width: 150px; +} + +.groupable-header-border { + border-left: 1px solid #C6C3C3; + border-right: 1px solid #C6C3C3; +} + +td.alert-notification-threshold { + color: #E87900; + background-color: rgba(255, 231, 175, 0.75); +} + +td.alert-disable-threshold { + color: #F50000; + background-color: rgba(255, 190, 190, 0.75); +} + +span.compact { + height: 16px; +} + +/** Quick view tooltip*/ +.quick-view-tooltip { + width: 470px; + display: inline-block; + padding-top: 0; + margin-left: 0px; +} + +.quick-view-tooltip > div.title { + width: 444px; + position: absolute; + top: 20px; + left: 10px; + color: #808080; + font-weight: 100; +} + +.quick-view-tooltip > div.title .icon { + position: relative; + top: -2px; + left: -7px; + background: url(../images/sprites.png) no-repeat -42px -67px; + float: right; + padding: 0px 13px 0 0px; +} + +.quick-view-tooltip .loading-overlay { + top: 94px; + height: 57px; + left: 1px; + /*+opacity:35%;*/ + filter: alpha(opacity=35); + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=35); + -moz-opacity: 0.35; + opacity: 0.35; +} + +.quick-view-tooltip > div.title span.title { +} + +.quick-view-tooltip .container { + border: 1px solid #9EA2A5; + background: #FFFFFF; + width: 471px; + min-height: 100px; + height: auto; + overflow: hidden; + display: inline-block; + /*+box-shadow:0px 7px 9px #676F76;*/ + -moz-box-shadow: 0px 7px 9px #676F76; + -webkit-box-shadow: 0px 7px 9px #676F76; + -o-box-shadow: 0px 7px 9px #676F76; + box-shadow: 0px 7px 9px #676F76; +} + +/*** Quick view detail view*/ +.quick-view-tooltip .detail-view { +} + +.quick-view-tooltip .detail-view .main-groups { + width: 456px; + height: 170px; + position: absolute; + padding-top: 7px; + top: 55px; + border: 1px solid #808080; + border-left: none; + border-right: none; + overflow: hidden; + /*+box-shadow:0px 1px #E6E6E6;*/ + -moz-box-shadow: 0px 1px #E6E6E6; + -webkit-box-shadow: 0px 1px #E6E6E6; + -o-box-shadow: 0px 1px #E6E6E6; + box-shadow: 0px 1px #E6E6E6; +} + +.quick-view-tooltip .detail-view .actions { +} + +.quick-view-tooltip .detail-view .tagger { + display: none; +} + +.quick-view-tooltip .detail-view ul { + display: none !important; +} + +.quick-view-tooltip .detail-view .ui-tabs-panel { + display: inline-block; + width: 100% !important; + float: left; + height: auto; + overflow: hidden; +} + +.quick-view-tooltip .detail-view .details { + display: inline-block; + height: auto; + padding-bottom: 224px; +} + +.quick-view-tooltip .detail-view .detail-group { + width: 365px; + margin: 0; + padding: 0; + left: -9px; + background: none; + border: none; +} + +.quick-view-tooltip .detail-view .detail-group table { + margin: 0; + border: none; + background: none; +} + +.quick-view-tooltip .detail-view .detail-group table tr { + background: none; +} + +.quick-view-tooltip .detail-view .detail-group table td.name { + color: #000000 !important; + padding: 0px 29px 0px 5px !important; + font-size: 13px; +} + +.quick-view-tooltip .detail-view .detail-group table td.value { + font-size: 12px; + /*+text-shadow:0px 1px #EAEAEA;*/ + -moz-text-shadow: 0px 1px #EAEAEA; + -webkit-text-shadow: 0px 1px #EAEAEA; + -o-text-shadow: 0px 1px #EAEAEA; + text-shadow: 0px 1px #EAEAEA; + overflow: hidden; +} + +.quick-view-tooltip .detail-view .detail-group table td.value input[type=text] { + width: 258px; + height: 10px; + margin-left: 0px; +} + +.quick-view-tooltip .detail-view .detail-group .main-groups table td.value span { + height: 25px; + top: 7px; +} + +.quick-view-tooltip .detail-view .detail-group.actions { + position: relative; + top: 202px; + float: left; + width: 100%; + height: auto; +} + +.quick-view-tooltip .detail-view .detail-group.actions .button { + top: 160px; +} + +.quick-view-tooltip .detail-view .detail-group.actions .action.text { + width: 112px; + height: 41px; + background: none; + border: none; + float: left; + margin-left: 5px; + display: inline-block; +} + +.quick-view-tooltip .detail-view .detail-group.actions .action.text:hover { + /*+box-shadow:none;*/ + -moz-box-shadow: none; + -webkit-box-shadow: none; + -o-box-shadow: none; + box-shadow: none; +} + +.quick-view-tooltip .detail-view .detail-group.actions .action.text .icon { + display: block; + float: left; + width: 4px; +} + +.quick-view-tooltip .detail-view .detail-group.actions .action.text .label { + width: 81px; + display: block; + float: right; + font-size: 11px; + color: #454C53; + /*+text-shadow:0px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px #FFFFFF; + -o-text-shadow: 0px 1px #FFFFFF; + text-shadow: 0px 1px #FFFFFF; + text-indent: 0px; +} + +.quick-view-tooltip .detail-view .detail-group.actions .action.text:hover .label { + color: #000000; +} + +.quick-view-tooltip .detail-view .detail-group.actions .detail-actions { + width: 460px; + height: auto; + background: none; + vertical-align: top; + position: relative; + top: 27px; + float: left; +} + +.quick-view-tooltip .detail-view .detail-group.actions td.view-all { + position: relative; + left: 0px; + top: 26px; + float: left; + height: 26px; + /*+box-shadow:inset 0px 1px #FFFFFF;*/ + -moz-box-shadow: inset 0px 1px #FFFFFF; + -webkit-box-shadow: inset 0px 1px #FFFFFF; + -o-box-shadow: inset 0px 1px #FFFFFF; + box-shadow: inset 0px 1px #FFFFFF; +} + +.quick-view-tooltip .detail-view .detail-actions a { + background: none; + width: 30px; +} + +/*Details page*/ +.detail-view { + padding: 0 0 0 14px; +} + +.ui-tabs ul.ui-tabs-nav { + margin-top: 42px; + display: block; + width: 100%; + height: 41px; + float: left; + border: none; + overflow: hidden; + z-index: 2; + /*+placement:shift 0px 2px;*/ + position: relative; + left: 0px; + top: 2px; +} + +.ui-tabs .info { + background: #EFEFEF; + width: 91%; + height: auto; + overflow: visible; + padding: 14px 14px 0; + border: 1px dashed #D7D7D7; + /*+box-shadow:inset 0px 1px 2px #FFFFFF;*/ + -moz-box-shadow: inset 0px 1px 2px #FFFFFF; + -webkit-box-shadow: inset 0px 1px 2px #FFFFFF; + -o-box-shadow: inset 0px 1px 2px #FFFFFF; + box-shadow: inset 0px 1px 2px #FFFFFF; + margin: 10px; + display: inline-block; +} + +.ui-tabs .info li { + font-size: 12px; + color: #3E4C59; + margin: 0 0 18px; +} + +.ui-tabs .info li strong { + font-weight: bold; + color: #506273; +} + +.ui-tabs ul.ui-tabs-subnav li { + display: block; + float: left; +} + +.ui-tabs li a { + float: left; + padding: 15px 10px; + min-width: 91px; + text-align: center; + font-size: 11px; + margin-right: 5px; + color: #4E6070; + text-decoration: none; + /*+placement:shift 0px 2px;*/ + position: relative; + left: 0px; + top: 2px; +} + +.ui-tabs li { + float: left; +} + +.ui-tabs ul li.ui-state-default a { + padding-bottom: 10px; + border: 1px solid #D9D9D9; + /*+border-radius:4px 4px 0 0;*/ + -moz-border-radius: 4px 4px 0 0; + -webkit-border-radius: 4px 4px 0 0; + -khtml-border-radius: 4px 4px 0 0; + border-radius: 4px 4px 0 0; + background: #F0F0F0; +} + +.project-view .ui-tabs ul li.ui-state-default a { + background: #6D747D; + /*+box-shadow:inset -1px -2px 12px #596066;*/ + -moz-box-shadow: inset -1px -2px 12px #596066; + -webkit-box-shadow: inset -1px -2px 12px #596066; + -o-box-shadow: inset -1px -2px 12px #596066; + box-shadow: inset -1px -2px 12px #596066; + color: #FFFFFF; + font-weight: bold; + /*+text-shadow:0px -1px 1px #3A3E42;*/ + -moz-text-shadow: 0px -1px 1px #3A3E42; + -webkit-text-shadow: 0px -1px 1px #3A3E42; + -o-text-shadow: 0px -1px 1px #3A3E42; + text-shadow: 0px -1px 1px #3A3E42; +} + +.ui-tabs ul li.ui-state-hover a { + text-decoration: underline; + color: #000000; +} + +.ui-tabs ul li.ui-state-active { + display: block; +} + +.ui-tabs ul li.ui-state-active a { + background: #FFFFFF; + padding-bottom: 12px; +} + +.project-view .ui-tabs ul li.ui-state-hover a { + background: #878E97 0px 8px; +} + +.project-view .ui-tabs ul li.ui-state-active a { + background: #DBDDDF; + color: #4F6270; + font-weight: bold; + /*+text-shadow:0px 0px #FFFFFF;*/ + -moz-text-shadow: 0px 0px #FFFFFF; + -webkit-text-shadow: 0px 0px #FFFFFF; + -o-text-shadow: 0px 0px #FFFFFF; + text-shadow: 0px 0px #FFFFFF; + /*+box-shadow:0px 0px;*/ + -moz-box-shadow: 0px 0px; + -webkit-box-shadow: 0px 0px; + -o-box-shadow: 0px 0px; + box-shadow: 0px 0px; + -moz-box-shadow: 0px 0px none; + -webkit-box-shadow: 0px 0px none; + -o-box-shadow: 0px 0px none; +} + +.ui-tabs li.ui-state-active.first a, +.ui-tabs li.ui-state-default.first a { + /*+border-radius:4px 4px 0 0;*/ + -moz-border-radius: 4px 4px 0 0; + -webkit-border-radius: 4px 4px 0 0; + -khtml-border-radius: 4px 4px 0 0; + border-radius: 4px 4px 0 0; + border: 1px solid #E2DDDD; + border: 1px solid #E2DDDD; +} + +.ui-tabs li.ui-state-active.last a, +.ui-tabs li.ui-state-default.last a { +} + +.ui-tabs li.ui-state-active.first.last a, +.ui-tabs li.ui-state-default.first.last a { + /*+border-radius:4px 4px 0 0;*/ + -moz-border-radius: 4px 4px 0 0; + -webkit-border-radius: 4px 4px 0 0; + -khtml-border-radius: 4px 4px 0 0; + border-radius: 4px 4px 0 0; +} + +.ui-tabs .ui-tabs-hide { + display: none !important; +} + +.ui-tabs div.ui-tabs-panel { + border: 1px solid #D9D9D9; + clear: both; + height: 78%; + width: 97%; + padding-top: 7px; + overflow: auto; + overflow-x: hidden; + height: 591px; + margin: -5px 0 0; +} + +.detail-view .main-groups { + max-height: 407px; + overflow: auto; + overflow-x: hidden; + width: 100%; + /*[empty]padding:;*/ + margin-right: 12px; +} + +.detail-view.edit-mode .main-groups { + max-height: 360px; +} + +.detail-group table { + width: 98%; + font-size: 12px; + border-bottom: 1px solid #DFDFDF; + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f7f7f7', endColorstr='#eaeaea',GradientType=0 ); + margin-top: 10px; +} + +.detail-group table tr, +.detail-group table td { + vertical-align: middle; + border: none; + cursor: default; +} + +.detail-group table tr.odd { + background: none; +} + +.details.group-multiple table { + border: none; + border-top: none; +} + +.details.group-multiple table.header { + width: 94%; + margin-bottom: 1px; +} + +.details.group-multiple table tbody { + border-top: 1px solid #F2F0F0; +} + +.detail-group .main-groups table td.name { + width: 113px; + color: #6D6D6D; + font-weight: bold; + padding: 14px 12px 13px 13px; + border: none; + text-indent: 0; +} + +.detail-group .main-groups table td.value { + text-indent: 0; +} + +.detail-group .main-groups table td.value > span { + display: block; + height: 30px; + overflow: auto; + position: relative; + top: 9px; + float: left; + width: 245px; +} + +.detail-group .main-groups table td.value > span.copypasteenabledvalue { + text-overflow: ellipsis; + -o-text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +.detail-group .main-groups table td.value > .copypasteactive { + display: auto; + white-space: nowrap; + overflow: none; +} + +div.copypasteicon { + background: url("../images/sprites.png") no-repeat -271px -65px; + float: left; + height: 21px; + margin-left: 6px; + margin-top: 0px; + width: 18px; +} + +div.copypasteicon:hover { + background: url("../images/sprites.png") no-repeat -271px -646px; +} + +.detail-group .main-groups table td.value > span.copypasteenabledvalue { + text-overflow: ellipsis; + -o-text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +.detail-group .main-groups table td.value > .copypasteactive { + display: auto; + white-space: nowrap; + overflow: none; +} + +div.copypasteicon { + background: url("../images/sprites.png") no-repeat -271px -65px; + float: left; + height: 21px; + margin-left: 6px; + margin-top: 0px; + width: 18px; +} + +div.copypasteicon:hover { + background: url("../images/sprites.png") no-repeat -271px -646px; +} + +.detail-group .main-groups table td.value > span select { + width: 100% !important; +} + +.detail-group .main-groups table td.value .view-all { + cursor: pointer; + /*[empty]height:;*/ + /*+border-radius:4px 0 0 4px;*/ + -moz-border-radius: 4px 0 0 4px; + -webkit-border-radius: 4px 0 0 4px; + -khtml-border-radius: 4px 0 0 4px; + border-radius: 4px 0 0 4px; + float: right; + margin: 7px 0 0; + padding: 0px; +} + +.detail-group .main-groups table td.value .view-all span { + display: block; + float: left; + padding: 5px 2px 8px 4px; + background: url(../images/gradients.png) repeat-x 0px -529px; + border-left: 1px solid #9FA2A6; + /*+border-radius:4px 0 0 4px;*/ + -moz-border-radius: 4px 0 0 4px; + -webkit-border-radius: 4px 0 0 4px; + -khtml-border-radius: 4px 0 0 4px; + border-radius: 4px 0 0 4px; + margin-top: -5px; +} + +.detail-group .main-groups table td.value .view-all .end { + background: url(../images/sprites.png) no-repeat 100% -397px; + float: right; + width: 22px; + height: 25px; + padding: 0px; + margin: -6px 0px 0px; +} + +.detail-group .main-groups table td.value .view-all:hover { + background-position: 100% -431px; +} + +.detail-group .main-groups table td.value .view-all:hover span { + background-position: 0px -566px; +} + +.detail-group .main-groups table td.value .view-all:hover div.end { + background-position: -618px -430px; +} + +.detail-view .detail-group .button.add { + clear: both; + margin: 0px 21px 13px 0 !important; +} + +.detail-view .details.group-multiple { + float: left; + width: 100%; + margin-bottom: 30px; +} + +.detail-view .details.group-multiple .main-groups { + overflow: visible; + width: 98%; + margin-bottom: 35px; +} + +/*List-view: subselect dropdown*/ +.list-view .subselect { + width: 116px; + display: block; + float: left; + background: url(../images/bg-gradients.png) 0px -42px; + padding: 0; + margin: 8px 0 1px 7px; + clear: both; + border: 1px solid #A8A7A7; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; +} + +.list-view .subselect span { + margin: 4px 0 0 12px; +} + +.list-view .subselect span.info { + font-size: 10px; + white-space: nowrap; +} + +.list-view .subselect select { + width: 85%; + margin: 5px 0 4px; + font-size: 10px; +} + +.detail-group .main-groups table td.value .view-all:hover { + background-position: 100% -431px; +} + +.panel.always-maximized .detail-group .main-groups table td.value span { + width: 565px; +} + +.detail-group.head table td.name { + padding: 20px 0px 17px; +} + +.detail-view .button.done, +.detail-view .button.cancel { + display: inline-block; + color: #FFFFFF; + font-size: 12px; + font-weight: bold; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; + /*+text-shadow:0px -1px 2px #000000;*/ + -moz-text-shadow: 0px -1px 2px #000000; + -webkit-text-shadow: 0px -1px 2px #000000; + -o-text-shadow: 0px -1px 2px #000000; + text-shadow: 0px -1px 2px #000000; + /*+box-shadow:0px 1px 4px #ADADAD;*/ + -moz-box-shadow: 0px 1px 4px #ADADAD; + -webkit-box-shadow: 0px 1px 4px #ADADAD; + -o-box-shadow: 0px 1px 4px #ADADAD; + box-shadow: 0px 1px 4px #ADADAD; + cursor: pointer; + margin: 0 0 0 12px; + padding: 9px 20px; + background: url(../images/bg-gradients.png) 0px -221px; + /*+placement:shift -1px 550px;*/ + position: relative; + left: -1px; + top: 550px; + position: absolute; +} + +.detail-view .button.cancel { + background-position: 0px -795px; + left: 85px; + color: #808080; + /*+text-shadow:0px -1px 2px #000000;*/ + -moz-text-shadow: 0px -1px 2px #000000; + -webkit-text-shadow: 0px -1px 2px #000000; + -o-text-shadow: 0px -1px 2px #000000; + text-shadow: 0px -1px 2px #000000; + -moz-text-shadow: 0px -1px 2px #CCCCCC; + -webkit-text-shadow: 0px -1px 2px #CCCCCC; + -o-text-shadow: 0px -1px 2px #CCCCCC; + text-shadow: 0px -1px 2px #CCCCCC; +} + +.detail-view .button.done:hover { + background-position: 0px -950px; + /*+box-shadow:inset 0px 1px 3px #000000;*/ + -moz-box-shadow: inset 0px 1px 3px #000000; + -webkit-box-shadow: inset 0px 1px 3px #000000; + -o-box-shadow: inset 0px 1px 3px #000000; + box-shadow: inset 0px 1px 3px #000000; +} + +.detail-view .button.cancel:hover { + background-position: 0px -834px; +} + +div.group-multiple div.detail-group table { + margin-top: -1px; +} + +div.group-multiple div.detail-group table.header { + border: none; + margin-top: 11px; +} + +div.group-multiple div.detail-group table.header thead th { + background: transparent; + border: none; +} + +div.ui-tabs-panel table span.none { + color: #9D9D9D; +} + +#user div.options .arrow { +} + +div.detail-group td.view-all div.view-all { + width: auto; + float: right; +} + +div.detail-group td.view-all a { + font-size: 13px; + display: block; + text-decoration: none; + color: #0373B7; + /*+text-shadow:0px 1px 2px #FFFFFF;*/ + -moz-text-shadow: 0px 1px 2px #FFFFFF; + -webkit-text-shadow: 0px 1px 2px #FFFFFF; + -o-text-shadow: 0px 1px 2px #FFFFFF; + text-shadow: 0px 1px 2px #FFFFFF; + float: left; + font-weight: 100; +} + +div.detail-group td.view-all:hover a { + background-position: 0px -566px; +} + +div.detail-group td.view-all a span { + /*+placement:shift -4px -1px;*/ + position: relative; + left: -4px; + top: -1px; +} + +div.detail-group td.view-all:hover a span { + text-decoration: underline; + color: #000000; +} + +div.detail-group td.view-all div.view-all div.end { + display: none; + float: left; + width: 15px; + height: 25px; + background: url(../images/sprites.png) -617px -398px; +} + +div.detail-group td.view-all:hover div.view-all div.end { + background-position: -617px -431px; +} + +div.details div.detail-group td.value input, +div.details div.detail-group td.value select { + width: 282px; +} + +div.details div.detail-group td.value input, +div.details div.detail-group td.value input[type=checkbox] { + float: left; + width: 15px; + margin-left: 10px; +} + +div.details div.detail-group td.value input, +div.details div.detail-group td.value input[type=text] { + width: 93%; +} + +div.details .main-groups label.error { + position: absolute; + right: 10%; + top: 6px; +} + +.detail-view td.view-all.multiple { + max-width: 145px; + display: block; + float: left; + height: 28px; + margin-left: 0; + text-align: left; +} + +/*** Actions*/ +div.detail-group.actions { + padding: 0; + margin: 0; +} + +div.detail-group.actions table { + padding: 0; +} + +div.detail-group.actions tr { + margin: 0; +} + +div.detail-group.actions td { + height: 50px; + vertical-align: middle; +} + +.details.group-multiple div.detail-group.actions { + float: right; + max-width: 75%; + height: 23px; + position: relative; + margin: -15px 0 -5px; +} + +.details.group-multiple div.detail-group.actions table { + background: none; +} + +.details.group-multiple div.detail-group.actions td.detail-actions { + background: none; + display: block; + height: 35px; + float: right; + padding: 0; + min-width: 120px; +} + +.details.group-multiple div.detail-group.actions .detail-actions .action { + float: left; + width: 32px; + /*+placement:shift 11px 7px;*/ + position: relative; + left: 11px; + top: 7px; +} + +.details.group-multiple div.detail-group.actions .detail-actions .action a { + background: none; + width: 31px; +} + +.detail-group table td.detail-actions { + height: 26px; +} + +.detail-group table td.detail-actions.full-length { + display: block; + width: 99%; + float: left; +} + +.detail-group table td.detail-actions .action.text { + padding: 0px 6px 0px 0px; + cursor: pointer; + display: inline-block; + float: right; + margin-right: 8px; + border: 1px solid #C2C2C2; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + background: url(../images/bg-gradients.png) repeat-x 0px -83px; +} + +.detail-group table td.detail-actions .action.text .label { + font-size: 12px; + /*+placement:shift -1px 8px;*/ + position: relative; + left: -1px; + top: 8px; +} + +.detail-group table td.detail-actions .action.text:hover { + /*+box-shadow:inset 0px 1px 3px #171717;*/ + -moz-box-shadow: inset 0px 1px 3px #171717; + -webkit-box-shadow: inset 0px 1px 3px #171717; + -o-box-shadow: inset 0px 1px 3px #171717; + box-shadow: inset 0px 1px 3px #171717; +} + +.detail-group table td.detail-actions div.buttons { +} + +.detail-group table td.detail-actions a { + display: block; + float: left; + text-indent: -9999px; + width: 30px; + height: 25px; + margin: 0; +} + +.detail-group table td.detail-actions a:hover { + background-position: -417px -43px; +} + +.detail-group table td.detail-actions div.action.first a { + background-position: -385px -11px; + width: 32px; +} + +.detail-group table td.detail-actions div.action.first a:hover { + background-position: -385px -43px; +} + +.detail-group table td.detail-actions div.action.last a { + background-position: -596px -11px; + width: 30px; +} + +.detail-group table td.detail-actions div.action.last a:hover { + background-position: -596px -43px; +} + +.detail-group table td.detail-actions div.action.single a { + width: 31px; + height: 26px; + background-position: -414px -625px; +} + +.detail-group table td.detail-actions div.action.text a { + background: none; +} + +.detail-group table td.detail-actions div.action.single a:hover { + background-position: -414px -587px; +} + +.detail-group table td.detail-actions a span.icon { + background-image: url(../images/sprites.png); + padding: 10px; + display: block; +} + +/*Header*/ +#header { + width: 100%; + height: 135px; + background: url(../images/overlay-pattern.png) repeat center, #2c2c2c url(../images/sky.jpg) no-repeat center; + background-size: auto, cover; + position: relative; +} + +#header div.button { + font-size: 12px; + color: #FFFFFF; + cursor: pointer; +} + +#header.nologo div.logo { + width: 1224px; + height: 47px; + margin: auto; + /*+placement:shift 0px 15px;*/ + position: relative; + left: 0px; + top: 15px; +} + +#header div.controls { + width: 1226px; + height: 48px; + position: relative; + margin: 27px auto 0; + padding-top: 13px; + /*+border-radius:4px 4px 0 0;*/ + -moz-border-radius: 4px 4px 0 0; + -webkit-border-radius: 4px 4px 0 0; + -khtml-border-radius: 4px 4px 0 0; + border-radius: 4px 4px 0 0; +} + +#header div.controls.nologo { + background: #666666; + background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzY2NjY2NiIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiMzZDNkM2QiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+); + background: -moz-linear-gradient(top, #666666 0%, #3d3d3d 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#666666), color-stop(100%,#3d3d3d)); + background: -webkit-linear-gradient(top, #666666 0%,#3d3d3d 100%); + background: -o-linear-gradient(top, #666666 0%,#3d3d3d 100%); + background: -ms-linear-gradient(top, #666666 0%,#3d3d3d 100%); + background: linear-gradient(to bottom, #666666 0%,#3d3d3d 100%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#666666', endColorstr='#3d3d3d',GradientType=0 ); + /*+box-shadow:0px -1px 6px #0E3955;*/ + -moz-box-shadow: 0px -1px 6px #0E3955; + -webkit-box-shadow: 0px -1px 6px #0E3955; + -o-box-shadow: 0px -1px 6px #0E3955; + box-shadow: 0px -1px 6px #0E3955; +} + +.button { + float: left; + background: url(../images/buttons.png) no-repeat; + cursor: pointer; +} + +#header div.notifications { + background: transparent; + float: right; + height: 18px; + padding: 1px 0 0; + /*+placement:shift -174px -57px;*/ + position: relative; + left: -239px; + top: -57px; +} + +#header div.notifications:after { + content: "|"; + /*+placement:shift 28px 7px;*/ + position: relative; + left: 28px; + top: 7px; +} + +#header div.notifications span { + position: relative; + top: 5px; + left: 7px; + /*+text-shadow:0px -1px 1px #464646;*/ + -moz-text-shadow: 0px -1px 1px #464646; + -webkit-text-shadow: 0px -1px 1px #464646; + -o-text-shadow: 0px -1px 1px #464646; + text-shadow: 0px -1px 1px #464646; +} + +#header div.notifications:hover { + color: #5FAAF7; +} + +#header div.notifications div.total { + width: 22px; + height: 19px; + float: left; + margin: 3px; + background: url(../images/sprites.png) no-repeat -593px -870px; + color: #FFFFFF; + font-size: 11px; + /*+text-shadow:0px -1px #6C7283;*/ + -moz-text-shadow: 0px -1px #6C7283; + -webkit-text-shadow: 0px -1px #6C7283; + -o-text-shadow: 0px -1px #6C7283; + text-shadow: 0px -1px #6C7283; + -moz-text-shadow: 0px -1px 0px #6C7283; + -webkit-text-shadow: 0px -1px 0px #6C7283; + -o-text-shadow: 0px -1px 0px #6C7283; +} + +#header div.notifications div.total.pending { + background-position: -593px -846px; + font-weight: bold; +} + +#header div.notifications div.total span { + /*+placement:shift 0px 3px;*/ + position: relative; + left: 0px; + top: 3px; + text-align: center; + display: block; + width: 21px; + font-size: 12px; +} + +#user { + height: 30px; + margin: 0; + position: absolute; + top: -47px; + left: 1025px; + cursor: default !important; + display: inline-block; + float: left; + background: transparent; +} + +#user div.name { + display: inline-block; + float: left; + padding: 9px 18px 7px 12px; + border-right: none; + /*[empty]border-top:;*/ + min-width: 110px; + max-width: 220px; + text-align: center; + height: 12px; + overflow: hidden; + /*+text-shadow:0px -1px 1px #464646;*/ + -moz-text-shadow: 0px -1px 1px #464646; + -webkit-text-shadow: 0px -1px 1px #464646; + -o-text-shadow: 0px -1px 1px #464646; + text-shadow: 0px -1px 1px #464646; + margin: 0; +} + +#user div.options { + float: left; + width: 31px; + height: 28px; + /*+placement:shift 0px 0px;*/ + position: relative; + left: 0px; + top: 0px; + background-position: 0px -867px; + cursor: pointer; +} + +#user div.options:hover { +} + +#user div.options .arrow { + width: 11px; + height: 8px; + background: url(../images/buttons.png) -402px -23px; + /*+placement:shift 8px 11px;*/ + position: relative; + left: 8px; + top: 11px; +} + +/** Zone filter (mixed zone management)*/ +#header .zone-filter { + float: left; + width: 111px; + margin: 9px 20px 0 2px; +} + +#header .zone-filter label { + position: absolute; + top: -3px; + color: #FFFFFF; + font-size: 11px; +} + +#header .zone-filter select { + width: 100%; + font-size: 12px; + border: 1px solid #000000; + border-bottom: #FFFFFF; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + background: #ECECEC; + margin-top: 2px; +} + +/*Navigation*/ +#navigation, +#browser { +} + +#navigation { + width: 230px; + position: relative; + float: left; + /*+box-shadow:inset -1px 4px 7px #DDDDDD;*/ + -moz-box-shadow: inset -1px 4px 7px #DDDDDD; + -webkit-box-shadow: inset -1px 4px 7px #DDDDDD; + -o-box-shadow: inset -1px 4px 7px #DDDDDD; + box-shadow: inset -1px 4px 7px #DDDDDD; + background: #EDE8E8; + background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjYlIiBzdG9wLWNvbG9yPSIjZWRlOGU4IiBzdG9wLW9wYWNpdHk9IjEiLz4KICA8L2xpbmVhckdyYWRpZW50PgogIDxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxIiBoZWlnaHQ9IjEiIGZpbGw9InVybCgjZ3JhZC11Y2dnLWdlbmVyYXRlZCkiIC8+Cjwvc3ZnPg==); + background: -moz-linear-gradient(top, #ffffff 0%, #ede8e8 6%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(6%,#ede8e8)); + background: -webkit-linear-gradient(top, #ffffff 0%,#ede8e8 6%); + background: -o-linear-gradient(top, #ffffff 0%,#ede8e8 6%); + background: -ms-linear-gradient(top, #ffffff 0%,#ede8e8 6%); + background: linear-gradient(to bottom, #ffffff 0%,#ede8e8 6%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#ede8e8',GradientType=0 ); +} + +.project-view #navigation { + background: #6D747D; +} + +#navigation ul { + height: 700px; + padding-top: 29px; +} + +.project-view #navigation ul { + border-right: 1px solid #464C53; + background: #6D747D; +} + +#navigation ul li { + height: 42px; + cursor: pointer; + border-bottom: 1px solid #D2D2D2; +} + +.project-view #navigation ul li { + background-image: url(../images/bg-nav-item-project-view.png); + background-position: 0px 0px; + border: none; +} + +.project-view #navigation ul li span { + color: #FFFFFF; + /*+text-shadow:0px 1px 1px #000000;*/ + -moz-text-shadow: 0px 1px 1px #000000; + -webkit-text-shadow: 0px 1px 1px #000000; + -o-text-shadow: 0px 1px 1px #000000; + text-shadow: 0px 1px 1px #000000; +} + +#navigation ul li:hover, +#navigation ul li.active { + width: 230px; + background: #2C5D7B; + /*+box-shadow:inset 0px 0px 7px #000000;*/ + -moz-box-shadow: inset 0px 0px 7px #000000; + -webkit-box-shadow: inset 0px 0px 7px #000000; + -o-box-shadow: inset 0px 0px 7px #000000; + box-shadow: inset 0px 0px 7px #000000; +} + +#navigation ul li.disabled { + /*+opacity:60%;*/ + filter: alpha(opacity=60); + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=60); + -moz-opacity: 0.6; + opacity: 0.6; +} + +.project-view #navigation ul li:hover, +.project-view #navigation ul li.active { + background: url(../images/bg-nav-item-active-project-view.png); + background-position: 0px 0px; + width: 230px; +} + +.project-view #navigation ul li.disabled:hover { + background: #D5D5D5; + color: #596D7F; + cursor: default; +} + +#navigation ul li:hover span, +#navigation ul li.active span { + color: #FFFFFF; + /*+text-shadow:0px 1px #000000;*/ + -moz-text-shadow: 0px 1px #000000; + -webkit-text-shadow: 0px 1px #000000; + -o-text-shadow: 0px 1px #000000; + text-shadow: 0px 1px #000000; +} + +#navigation ul li.disabled:hover { + cursor: not-allowed !important; +} + +#navigation ul li.disabled:hover span { + color: #596D7F; + /*+text-shadow:0px 0px;*/ + -moz-text-shadow: 0px 0px; + -webkit-text-shadow: 0px 0px; + -o-text-shadow: 0px 0px; + text-shadow: 0px 0px; + -moz-text-shadow: 0px 0px none; + -webkit-text-shadow: 0px 0px none; + -o-text-shadow: 0px 0px none; + -moz-text-shadow: none; + -webkit-text-shadow: none; + -o-text-shadow: none; +} + +#navigation ul li.last { + background-repeat: repeat; + background-position: 0px 0px; + /*[empty]color:;*/ +} + +#navigation ul li span { + /*+placement:shift 14px 13px;*/ + position: relative; + left: 14px; + top: 13px; + font-size: 12px; + color: #515151; + padding-left: 19px; + /*+text-shadow:0px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px #FFFFFF; + -o-text-shadow: 0px 1px #FFFFFF; + text-shadow: 0px 1px #FFFFFF; +} + +#navigation ul li span.icon { + background: url(../images/icons.png) no-repeat 0px 0px; + padding: 16px 16px 13px; + /*+placement:shift 17px 10px;*/ + position: relative; + left: 17px; + top: 10px; +} + +#navigation ul li.custom-icon span.icon { + display: block; + width: 50px; + height: 50px; + position: relative; + float: left; + margin-right: -47px; + background: none; +} + +#navigation ul li.custom-icon span.icon img { + width: 50px; + height: 50px; + float: left; + /*+placement:shift -6px -17px;*/ + position: relative; + left: -6px; + top: -17px; + position: absolute; + margin-right: -14px; +} + +/*Navigation icons*/ +#navigation ul li.dashboard span.icon, +#navigation ul li.dashboard-user span.icon { + background-position: -14px -18px; +} + +#navigation ul li.dashboard:hover span.icon, +#navigation ul li.dashboard-user:hover span.icon span.icon, +#navigation ul li.dashboard.active span.icon, +#navigation ul li.dashboard-user.active span.icon span.icon { + background-position: -23px -687px; +} + +#navigation ul li.instances span.icon { + background-position: -73px -18px; +} + +#navigation ul li.instances.active span.icon, +#navigation ul li.instances:hover span.icon { + background-position: -82px -686px; +} + +#navigation ul li.affinityGroups span.icon { + background-position: -73px -87px; +} + +#navigation ul li.affinityGroups.active span.icon, +#navigation ul li.affinityGroups:hover span.icon { + background-position: -82px -755px; +} + +#navigation ul li.storage span.icon { + background-position: -127px -19px; +} + +#navigation ul li.storage.active span.icon, +#navigation ul li.storage:hover span.icon { + background-position: -137px -687px; +} + +#navigation ul li.network span.icon { + background-position: -180px -20px; +} + +#navigation ul li.network.active span.icon, +#navigation ul li.network:hover span.icon { + background-position: -189px -690px; +} + +#navigation ul li.templates span.icon { + background-position: -233px -21px; +} + +#navigation ul li.templates.active span.icon, +#navigation ul li.templates:hover span.icon { + background-position: -242px -690px; +} + +#navigation ul li.projects span.icon { + background-position: -294px -21px; +} + +#navigation ul li.projects.active span.icon, +#navigation ul li.projects:hover span.icon { + background-position: -303px -690px; +} + +#navigation ul li.events span.icon { + background-position: -351px -23px; +} + +#navigation ul li.events.active span.icon, +#navigation ul li.events:hover span.icon { + background-position: -359px -692px; +} + +#navigation ul li.configuration span.icon { + background-position: -401px -21px; +} + +#navigation ul li.configuration.active span.icon, +#navigation ul li.configuration:hover span.icon { + background-position: -410px -690px; +} + +#navigation ul li.global-settings span.icon { + background-image: url(../images/sprites.png); + background-position: -143px -240px; +} + +#navigation ul li.global-settings.active span.icon, +#navigation ul li.global-settings:hover span.icon { + background-image: url(../images/sprites.png); + background-position: -366px -239px; +} + +#navigation ul li.accounts span.icon { + background-position: -458px -19px; +} + +#navigation ul li.accounts.active span.icon, +#navigation ul li.accounts:hover span.icon { + background-position: -467px -688px; +} + +#navigation ul li.system span.icon { + background-position: -569px -24px; +} + +#navigation ul li.system.active span.icon, +#navigation ul li.system:hover span.icon { + background-position: -578px -692px; +} + +#navigation ul li.domains span.icon { + background-position: -520px -21px; +} + +#navigation ul li.domains.active span.icon, +#navigation ul li.domains:hover span.icon { + background-position: -529px -690px; +} + +#navigation ul li.plugins span.icon { + background: url(../images/sprites.png) no-repeat -140px -291px; +} + +#navigation ul li.regions span.icon { + background: url(../images/sprites.png) no-repeat -141px -379px; +} + +#navigation ul li.regions.active span.icon, +#navigation ul li.regions:hover span.icon { + background: url(../images/sprites.png) no-repeat -365px -377px; +} + +/*Browser*/ +#browser { + width: 994px; + height: 100%; + max-width: 994px; + position: relative; + float: left; + overflow: hidden; +} + +#browser.panel-highlight { + overflow: visible; +} + +#browser.panel-highlight .panel { +} + +#browser div.panel { + height: 100%; + border-right: 1px solid #A5A5A5; + overflow: visible; + background-color: #F7F7F7; +} + +#browser div.panel.panel-highlight-wrapper { + display: inline-block; + background: none; + /*+border-radius:9px;*/ + -moz-border-radius: 9px; + -webkit-border-radius: 9px; + -khtml-border-radius: 9px; + border-radius: 9px; + margin-top: 7px; + /*+box-shadow:0px 0px 12px #000000;*/ + -moz-box-shadow: 0px 0px 12px #000000; + -webkit-box-shadow: 0px 0px 12px #000000; + -o-box-shadow: 0px 0px 12px #000000; + box-shadow: 0px 0px 12px #000000; + border: 3px solid #FFFFFF; + height: 542px; + overflow: hidden; + position: absolute; + z-index: 10000; + padding: 78px 0px 67px 51px; +} + +#browser div.panel.panel-highlight-wrapper .panel { + left: 20px !important; + height: 631px; + overflow: hidden; + top: 3px; +} + +.project-view #browser div.panel { + background: #6D747D; +} + +.ui-tabs div.ui-tabs-panel { + position: relative; +} + +.project-view .ui-tabs div.ui-tabs-panel { + background: #DBDDDF; +} + +#browser div.panel .shadow { + width: 10px; + height: 100%; + top: 0px; + left: -10px; + position: absolute; + background: url(../images/bg-panel-shadow.png) repeat-y 0px 0px; +} + +#browser.panel-highlight { + overflow: visible; +} + +#browser.panel-highlight .panel.highlighted { + /*+box-shadow:0px 10px 11px #5C5C5C;*/ + -moz-box-shadow: 0px 10px 11px #5C5C5C; + -webkit-box-shadow: 0px 10px 11px #5C5C5C; + -o-box-shadow: 0px 10px 11px #5C5C5C; + box-shadow: 0px 10px 11px #5C5C5C; + border: 5px solid #FFFFFF; + /*+border-radius:6px;*/ + -moz-border-radius: 6px; + -webkit-border-radius: 6px; + -khtml-border-radius: 6px; + border-radius: 6px; + margin-top: 21px; +} + +#browser.panel-highlight .panel > .shadow { + display: none; +} + +#browser .highlight-arrow { + width: 24px; + height: 19px; + background: url(../images/sprites.png) -590px -1295px; + position: absolute; + top: -22px; + left: 80px; +} + +/*Toolbar*/ +/*[clearfix]*/div.toolbar { + width: 100%; + height: 32px; + /*+box-shadow:0px 1px 4px #CFCFCF;*/ + -moz-box-shadow: 0px 1px 4px #CFCFCF; + -webkit-box-shadow: 0px 1px 4px #CFCFCF; + -o-box-shadow: 0px 1px 4px #CFCFCF; + box-shadow: 0px 1px 4px #CFCFCF; + /*+placement:shift 0px -1px;*/ + position: relative; + left: 0px; + top: -1px; + z-index: 6; + position: absolute; + top: 0px; + background: #ECECEC 0px -6px; +} + +.detail-view .ui-tabs-panel div.toolbar { + width: 968px; + background: transparent; + border: none; + margin-top: 8px; +} + +.project-view div.toolbar { + background: #808080 url(../images/bg-nav-item-active-project-view.png) 0px -210px; +} + +div.toolbar div.filters { + margin: 5px 0px 0 12px; +} + +div.toolbar div.filters label { + color: #3F3B3B; + font-size: 12px; + font-weight: 100; + display: block; + float: left; + padding: 5px 11px 0 0; +} + +.project-view div.toolbar div.filters label { + color: #FFFFFF; + /*+text-shadow:0px 1px 1px #000000;*/ + -moz-text-shadow: 0px 1px 1px #000000; + -webkit-text-shadow: 0px 1px 1px #000000; + -o-text-shadow: 0px 1px 1px #000000; + text-shadow: 0px 1px 1px #000000; +} + +div.toolbar div.filters select { + width: 142px; + border: 1px solid #808080; +} + +div.toolbar div.text-search { + float: right; + position: relative; +} + +div.toolbar div.text-search div.search-bar { + float: left; + width: 141px; + height: 20px; + margin: 5px 0 0 12px; + background: #FFFFFF; + border: 1px solid #8B7E7E; + z-index: 4; + position: relative; + border-right: 1px solid #8B8989; + /*+border-radius:4px 0 0 4px;*/ + -moz-border-radius: 4px 0 0 4px; + -webkit-border-radius: 4px 0 0 4px; + -khtml-border-radius: 4px 0 0 4px; + border-radius: 4px 0 0 4px; +} + +div.toolbar div.text-search div.search-bar input { + float: left; + border: none; + margin: 1px 0 0; + width: 90%; + height: 68%; +} + +div.toolbar div.text-search div.search-bar div.filter { + background: #FFFFFF; + width: 74px; + height: 15px; + float: left; + font-size: 12px; + text-align: center; + border-left: 1px solid #6D6D6D; + margin: 2px 0 0px; + padding: 1px; +} + +div.toolbar div.button.search { + background: url(../images/sprites.png) no-repeat -592px -328px; + width: 33px; + height: 22px; + /*+placement:shift -10px 5px;*/ + position: relative; + left: -10px; + top: 5px; + z-index: 3; + cursor: pointer; +} + +div.toolbar div.button.search:hover { + background-position: -592px -359px; +} + +div.toolbar div.button.add, +div.toolbar div.button.refresh, +div.toolbar div.button.add, +div.toolbar div.button.main-action, +.toolbar div.button.header-action, +.detail-group .button.add { + /*+placement:shift 0px 5px;*/ + position: relative; + left: 0px; + top: 5px; + background: #EAEAEA; + font-size: 12px; + font-weight: 100; + color: #000000; + margin: 0 10px 0 0; + cursor: pointer; + /*+text-shadow:0px 1px 1px #DEE5EA;*/ + -moz-text-shadow: 0px 1px 1px #DEE5EA; + -webkit-text-shadow: 0px 1px 1px #DEE5EA; + -o-text-shadow: 0px 1px 1px #DEE5EA; + text-shadow: 0px 1px 1px #DEE5EA; + padding: 5px 5px 5px 5px; + background: #F7F7F7; + background: rgb(247, 247, 247); + background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIxJSIgc3RvcC1jb2xvcj0iI2Y3ZjdmNyIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNlYWVhZWEiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+); + background: -moz-linear-gradient(top, rgba(247,247,247,1) 1%, rgba(234,234,234,1) 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(1%,rgba(247,247,247,1)), color-stop(100%,rgba(234,234,234,1))); + background: -webkit-linear-gradient(top, rgba(247,247,247,1) 1%,rgba(234,234,234,1) 100%); + background: -o-linear-gradient(top, rgba(247,247,247,1) 1%,rgba(234,234,234,1) 100%); + background: -ms-linear-gradient(top, rgba(247,247,247,1) 1%,rgba(234,234,234,1) 100%); + background: linear-gradient(to bottom, rgba(247,247,247,1) 1%,rgba(234,234,234,1) 100%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f7f7f7', endColorstr='#eaeaea',GradientType=0 ); + border: 1px solid #B7B7B7; + float: right; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; + height: 12px; +} + +div.toolbar div.button.add:hover, +div.toolbar div.button.refresh:hover, +div.toolbar div.button.main-action:hover, +.toolbar div.button.header-action:hover, +.detail-group .button.add:hover { + background: #E5E5E5; + /*+box-shadow:inset 0px 0px 5px #C3C3C3;*/ + -moz-box-shadow: inset 0px 0px 5px #C3C3C3; + -webkit-box-shadow: inset 0px 0px 5px #C3C3C3; + -o-box-shadow: inset 0px 0px 5px #C3C3C3; + box-shadow: inset 0px 0px 5px #C3C3C3; +} + +div.toolbar div.button.main-action span.icon { + background-image: url(../images/sprites.png); + display: block; + cursor: pointer; + width: 34px; + height: 20px; + float: left; + /*+placement:shift 0px -7px;*/ + position: relative; + left: 0px; + top: -7px; +} + +div.toolbar div.button.refresh { + float: right; + margin: 0 20px 0 0; +} + +div.toolbar div.button.refresh span { + background-image: url(../images/icons.png); + padding: 1px 1px 1px 16px; + background-position: -629px -232px; + background-repeat: no-repeat; +} + +div.toolbar div.button.add span, +.detail-group .button.add span.icon { + padding: 0px 0 3px 18px; + background: url(../images/icons.png) no-repeat -626px -209px; + /*+placement:shift 0px 0px;*/ + position: relative; + left: 0px; + top: 0px; +} + +#browser div.panel.selected div.toolbar { + border-right: 1px solid #43586B; +} + +/*** Advanced search*/ +#advanced_search { + width: 15px; + position: absolute; + left: 139px; + top: 4px; + z-index: 4; + background: none; +} + +#advanced_search .icon { + /*+opacity:56%;*/ + filter: alpha(opacity=56); + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=56); + -moz-opacity: 0.56; + opacity: 0.56; + background: url(../images/sprites.png) no-repeat -62px -162px; + padding: 10px; + position: absolute; + top: 1px; + left: -1px; + z-index: 10; +} + +#advanced_search:hover .icon { + /*+opacity:100%;*/ + filter: alpha(opacity=100); + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100); + -moz-opacity: 1; + opacity: 1; +} + +#advanced_search .form-container { + /*+opacity:91%;*/ + filter: alpha(opacity=91); + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=91); + -moz-opacity: 0.91; + opacity: 0.91; + /*+box-shadow:0px 5px 9px #B6B0B0;*/ + -moz-box-shadow: 0px 5px 9px #B6B0B0; + -webkit-box-shadow: 0px 5px 9px #B6B0B0; + -o-box-shadow: 0px 5px 9px #B6B0B0; + box-shadow: 0px 5px 9px #B6B0B0; + border: 1px solid #808080; + /*+border-radius:0 0 4px 4px;*/ + -moz-border-radius: 0 0 4px 4px; + -webkit-border-radius: 0 0 4px 4px; + -khtml-border-radius: 0 0 4px 4px; + border-radius: 0 0 4px 4px; + left: -290px; + top: 2px; + position: absolute; + display: inline-block; + background: #FFFFFF; + padding: 18px; + cursor: default; +} + +#advanced_search .form-container .name { + width: 66px; + float: left; +} + +#advanced_search .form-container .value { + width: 186px; + float: left; +} + +#advanced_search .form-container .form-item { + width: 268px; + height: 40px; + margin-bottom: 15px; +} + +#advanced_search .form-container .form-item input, +#advanced_search .form-container .form-item select { + width: 97%; + padding: 3px; +} + +#advanced_search input[type=submit] { + float: right; + background: url(../images/bg-gradients.png) 0px -220px; + /*+box-shadow:0px 2px 5px #858585;*/ + -moz-box-shadow: 0px 2px 5px #858585; + -webkit-box-shadow: 0px 2px 5px #858585; + -o-box-shadow: 0px 2px 5px #858585; + box-shadow: 0px 2px 5px #858585; + border: 1px solid #606060; + border-top: none; + color: #FFFFFF; + font-size: 12px; + font-weight: bold; + /*+text-shadow:0px 1px 1px #000000;*/ + -moz-text-shadow: 0px 1px 1px #000000; + -webkit-text-shadow: 0px 1px 1px #000000; + -o-text-shadow: 0px 1px 1px #000000; + text-shadow: 0px 1px 1px #000000; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + cursor: pointer; + padding: 8px 20px; +} + +#advanced_search input[type=submit]:hover { + /*+box-shadow:inset 0px 2px 3px #000000;*/ + -moz-box-shadow: inset 0px 2px 3px #000000; + -webkit-box-shadow: inset 0px 2px 3px #000000; + -o-box-shadow: inset 0px 2px 3px #000000; + box-shadow: inset 0px 2px 3px #000000; +} + +#advanced_search .button.cancel { + background: url(noen); + color: #9A9A9A; + font-size: 12px; + float: right; + /*+placement:shift -32px 13px;*/ + position: relative; + left: -32px; + top: 13px; + font-weight: bold; +} + +#advanced_search .button.cancel:hover { + color: #494949; +} + +/*** Panel controls*/ +#browser div.panel div.toolbar div.panel-controls { + float: right; + width: 42px; + height: 23px; + display: none; +} + +#browser div.panel.reduced div.toolbar div.panel-controls { + display: block; + float: left; + width: 194px; +} + +#browser div.panel.maximized div.toolbar div.panel-controls { + display: block; +} + +#browser div.panel div.toolbar div.panel-controls div.control { + width: 25px; + height: 26px; + background: url(../images/buttons.png) no-repeat -599px -335px; + float: right; + cursor: pointer; + margin-right: 6px; +} + +#browser div.panel.maximized.single div.toolbar div.panel-controls div.control { + display: none; +} + +#browser div.panel div.toolbar div.panel-controls div.control:hover { + background-position: -593px -309px; +} + +#browser div.panel.maximized div.toolbar div.panel-controls div.control { + background-position: -621px -334px; +} + +#browser div.panel.maximized div.toolbar div.panel-controls div.control:hover { + background-position: -617px -308px; +} + +/*** Section switcher*/ +div.panel div.toolbar div.section-switcher { + margin-top: 6px; + margin-left: 10px; + float: left; +} + +#browser div.panel.maximize-if-selected.selected div.toolbar div.panel-controls div.control { + display: none; +} + +div.toolbar div.section-switcher div.section-select { + float: right; + font-size: 12px; + font-weight: 100; +} + +div.toolbar div.section-switcher div.section { + float: left; + font-size: 11px; + font-weight: bold; + /*+border-radius:10px;*/ + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + -khtml-border-radius: 10px; + border-radius: 10px; + border-radius: 10px 10px 10px 10px; + /*+text-shadow:0px 1px 1px #EDEDED;*/ + -moz-text-shadow: 0px 1px 1px #EDEDED; + -webkit-text-shadow: 0px 1px 1px #EDEDED; + -o-text-shadow: 0px 1px 1px #EDEDED; + text-shadow: 0px 1px 1px #EDEDED; +} + +div.toolbar div.section-switcher div.section a { + background: url(../images/bg-section-switcher.png) repeat-x 0px -22px; + text-decoration: none; + display: block; + color: #516374; + padding: 5px 10px 3px; + border: 1px solid #979FA4; +} + +div.toolbar div.section-switcher div.section a.active { + background: url(../images/bg-section-switcher.png) repeat-x 0px -21px; + background-position: 0px 0px; + border: none; + border-bottom: 1px solid #CCD1D4; + padding-bottom: 2px; + padding-top: 6px; + /*+box-shadow:inset 0px 1px 5px #546874;*/ + -moz-box-shadow: inset 0px 1px 5px #546874; + -webkit-box-shadow: inset 0px 1px 5px #546874; + -o-box-shadow: inset 0px 1px 5px #546874; + box-shadow: inset 0px 1px 5px #546874; +} + +div.toolbar div.section-switcher div.section.first a { + /*+border-radius:4px 0 0 5px;*/ + -moz-border-radius: 4px 0 0 5px; + -webkit-border-radius: 4px 0 0 5px; + -khtml-border-radius: 4px 0 0 5px; + border-radius: 4px 0 0 5px; +} + +div.toolbar div.section-switcher div.section.last a { + /*+border-radius:0 4px 4px 0px;*/ + -moz-border-radius: 0 4px 4px 0px; + -webkit-border-radius: 0 4px 4px 0px; + -khtml-border-radius: 0 4px 4px 0px; + border-radius: 0 4px 4px 0px; +} + +div.toolbar div.section-switcher div.section.first.last a { + /*+border-radius:5px;*/ + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + -khtml-border-radius: 5px; + border-radius: 5px; + border-radius: 5px 5px 5px 5px; +} + +div.toolbar div.section-switcher div.section-select { + float: left; + height: 26px; +} + +.project-view div.toolbar div.section-switcher div.section-select { + background: transparent; +} + +div.toolbar div.section-switcher div.section-select select { + width: 100px; + height: 21px; + margin-right: 13px; + font-size: 12px; + border: 1px solid #808080; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; +} + +div.toolbar div.section-switcher div.section-select label { + margin: 0 9px 0 0; +} + +.project-view div.list-view div.toolbar div.section-switcher div.section-select label { + color: #FFFFFF; + /*+text-shadow:0px 1px 1px #000000;*/ + -moz-text-shadow: 0px 1px 1px #000000; + -webkit-text-shadow: 0px 1px 1px #000000; + -o-text-shadow: 0px 1px 1px #000000; + text-shadow: 0px 1px 1px #000000; +} + +/*Breadcrumbs*/ +div.toolbar div.filters { + margin: 5px 0px 0 12px; + width: 200px; + float: left; +} + +div.toolbar label { +} + +div.toolbar div.filters select { + width: 104px; + height: 21px; + font-size: 12px; + border: 1px solid #808080; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; + padding: 0px 0 0; + margin: 1px 0 0; +} + +#breadcrumbs { + height: 29px; + max-height: 29px; + background: #FFFFFF; + overflow: hidden; + width: 100%; +} + +.project-view #breadcrumbs { + background-image: url(../images/bg-breadcrumbs-project-view.png); + background-position: 0px 1px; + background-color: #828282; +} + +#breadcrumbs div.home { + width: auto; + height: 23px; + float: left; + /*+placement:shift -1px 0px;*/ + position: relative; + left: -1px; + top: 0px; + cursor: pointer; + z-index: 5; +} + +.project-view #breadcrumbs div.home { + background-position: -63px -98px; +} + +.project-view #breadcrumbs div.end { + background-position: -89px -98px; +} + +#breadcrumbs ul li, +#breadcrumbs div.active-project, +#breadcrumbs .home { + height: 21px; + float: left; + font-size: 13px; + color: #FFFFFF; + padding: 9px 5px 0px 0px; + cursor: pointer; + /*+placement:shift -13px 0px;*/ + position: relative; + left: -13px; + top: 0px; + position: relative; + margin: 0 0 0 2px; +} + +#breadcrumbs ul li:after, +#breadcrumbs .home:after { + content: ">"; + font-size: 11px; + /*+placement:shift 7px -1px;*/ + position: relative; + left: 7px; + top: -1px; + color: #C4C4C4; +} + +.project-view #breadcrumbs ul li { + color: #FFFFFF !important; +} + +#breadcrumbs ul li, +#breadcrumbs div.active-project, +#breadcrumbs .home { + /*+placement:shift 0px 0px;*/ + position: relative; + left: 0px; + top: 0px; + color: #63A9F1; + padding: 9px 5px 0px 8px; +} + +#breadcrumbs ul li:hover, +#breadcrumbs ul li.active, +#breadcrumbs ul li.maximized { + color: #000000; +} + +/*NOTE: End divs are not displayed per UI changes*/ +#breadcrumbs div.end { +} + +#breadcrumbs ul div.end { + /*+placement:shift -37px -1px;*/ + position: relative; + left: -37px; + top: -1px; + margin-right: 0px; + /*Disabled*/ + display: none; +} + +#breadcrumbs ul li { + position: relative; + /*+placement:shift -36px 0px;*/ + top: 0px; + font-size: 13px; +} + +#breadcrumbs div.active-project { + z-index: 2; + text-indent: 9px; + display: none; +} + +/*Group View*/ +div.panel div.view.group-thumbnail ul.groups li { + float: left; + width: 142px; + height: 80px; + background: url(../images/buttons.png) no-repeat -1px -399px; + margin: 16px -1px -5px 16px; + cursor: pointer; + position: relative; +} + +div.panel div.view.group-thumbnail ul.groups li.active { + background-position: -1px -489px; +} + +div.panel div.view.group-thumbnail ul.groups li.drop-hover { + background-position: -1px -310px !important; +} + +div.panel div.view.group-thumbnail ul.groups li.new { + background-position: -147px -401px; +} + +div.panel div.view.group-thumbnail ul.groups li.new.drop-hover { + background-position: -148px -312px !important; +} + +div.panel div.view.group-thumbnail ul.groups li span.name { + font-size: 12px; + color: #49596B; + /*+placement:shift 9px 7px;*/ + position: relative; + left: 9px; + top: 7px; + font-weight: bold; + /*+text-shadow:0px 1px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px 1px #FFFFFF; + -o-text-shadow: 0px 1px 1px #FFFFFF; + text-shadow: 0px 1px 1px #FFFFFF; + position: absolute; +} + +div.panel div.view.group-thumbnail ul.groups li span.vm-count { + font-size: 21px; + /*+placement:displace 54px 27px;*/ + position: absolute; + margin-left: 54px; + margin-top: 27px; + position: absolute; + color: #3A4857; +} + +/*Reduced view*/ +#browser div.panel.reduced .reduced-hide { + color: #BBB8B8; +} + +#browser div.panel.reduced div.toolbar .reduced-hide { + display: none; +} + +/*List view -- edit field*/ +div.view table td.editable div.edit { + width: 106%; + height: 20px; + /*+placement:shift 6px 0px;*/ + position: relative; + left: 6px; + top: 0px; +} + +div.view table td.truncated.editable div.edit { + top: 1px; + width: 285px; + left: 1px; +} + +div.view table td.editable div.edit input { + float: left; + width: 66%; + height: 17px; + border: none; + position: relative; + z-index: 1; +} + +.detail-view div.view table td.editable div.edit { + width: 116px; +} + +div.view table td.editable div.action { + float: left; + width: 16px; + height: 19px; + background: #FFFFFF url(../images/buttons.png) -614px -684px; + padding-left: 2px; + /*+placement:shift -2px 0px;*/ + position: relative; + left: -2px; + top: 0px; + cursor: pointer; +} + +div.view table td.editable div.action.save { + margin-left: 2px; +} + +div.view table td.editable div.action.cancel { + background-position: -628px -684px; +} + +/*** Actions*/ +table td.actions { + cursor: default; + /*Make fixed*/ + width: 200px; + min-width: 200px; + max-width: 200px; +} + +table td.actions span { + margin: 0 0 0 2px !important; +} + +table td.actions .action span.icon { + background-image: url(../images/sprites.png); + cursor: pointer; + width: 23px; + height: 21px; + float: left; +} + +table td.actions .action.disabled { +} + +table td.actions .action.disabled:hover .icon { +} + +table td.actions .action.disabled .icon { + cursor: not-allowed; + /*+opacity:20%;*/ + filter: alpha(opacity=20); + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=20); + -moz-opacity: 0.2; + opacity: 0.2; +} + +table tr.odd td.actions .action.disabled .icon { + background-color: #F2F0F0; +} + +table tr.even td.actions .action.disabled .icon { + background-color: #DFE1E3; +} + +table tr td.actions .action.text { + cursor: pointer; + display: inline-block; + border: 1px solid #C2C2C2; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + background: url(../images/bg-gradients.png) repeat-x 0px -83px; +} + +table tr td.actions .action.text:hover { + /*+box-shadow:inset 0px 1px 3px #171717;*/ + -moz-box-shadow: inset 0px 1px 3px #171717; + -webkit-box-shadow: inset 0px 1px 3px #171717; + -o-box-shadow: inset 0px 1px 3px #171717; + box-shadow: inset 0px 1px 3px #171717; +} + +table tr td.actions .action.text .label { + padding: 4px 0 0 4px; +} + +table tr td.actions .action.text .icon { + padding-bottom: 4px; +} + +table tr.selected td.actions .action.disabled .icon { + background-color: #CBDDF3; +} + +/*** Action icons +Dialogs*/ +.ui-dialog { + background: #FFFFFF; + text-align: left; + /*+box-shadow:0px -4px 15px #4C4A4A;*/ + -moz-box-shadow: 0px -4px 15px #4C4A4A; + -webkit-box-shadow: 0px -4px 15px #4C4A4A; + -o-box-shadow: 0px -4px 15px #4C4A4A; + box-shadow: 0px -4px 15px #4C4A4A; + position: absolute; + padding: 15px; +} + +.ui-dialog .ui-widget-content { + padding: 8px; + text-align: center; + display: inline-block; +} + +.ui-dialog .ui-widget-content .nothing-to-select { + width: 386px; + line-height: 21px; + text-align: left; + font-size: 16px; + color: #3D3D3D; + padding: 4px 25px 180px 28px; + /*+text-shadow:0px 1px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px 1px #FFFFFF; + -o-text-shadow: 0px 1px 1px #FFFFFF; + text-shadow: 0px 1px 1px #FFFFFF; + background: #FFFFFF; + margin: 57px 0 0; + /*+border-radius:10px;*/ + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + -khtml-border-radius: 10px; + border-radius: 10px; + border-radius: 10px 10px 10px 10px; +} + +.ui-dialog .ui-widget-content .nothing-to-select p { + margin: 18px 0 0; +} + +.ui-dialog .ui-widget-content .nothing-to-select .specify-ip { + margin-top: 28px; + padding-top: 21px; + font-size: 12px; + border-top: 1px solid #DFDFDF; +} + +.ui-dialog-buttonset { + width: 285px; + margin: 0; +} + +.ui-dialog .ui-button { + display: block; + cursor: pointer; + float: left; + width: 110px; + height: 31px; + border: none; + background: url(../images/gradients.png) -2px -481px; + border: 1px solid #BFBCBC; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; +} + +.ui-dialog .ui-button:hover { + background-position: -4px -426px; +} + +.ui-dialog.notice .close.ui-button { + background: transparent; + display: inline; + padding: 0; + float: right; + color: #516374; + /*+text-shadow:0px -1px 1px #FFFFFF;*/ + -moz-text-shadow: 0px -1px 1px #FFFFFF; + -webkit-text-shadow: 0px -1px 1px #FFFFFF; + -o-text-shadow: 0px -1px 1px #FFFFFF; + text-shadow: 0px -1px 1px #FFFFFF; +} + +.ui-dialog .ui-button.ok { + background-position: 0px -317px; + border: 1px solid #0065C5; + color: #FFFFFF; + /*+text-shadow:0px -1px 1px #011238;*/ + -moz-text-shadow: 0px -1px 1px #011238; + -webkit-text-shadow: 0px -1px 1px #011238; + -o-text-shadow: 0px -1px 1px #011238; + text-shadow: 0px -1px 1px #011238; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; +} + +.ui-dialog .ui-button.ok:hover { + background-position: -3px -368px; + border: 1px solid #004FF7; +} + +.ui-dialog.confirm .ui-button { + margin-top: 0px; + margin-left: 11px; +} + +.ui-dialog.confirm .ui-button.cancel { + margin-left: 50px; +} + +.ui-dialog span.message { + display: block; + text-align: center; + color: #445361; + font-size: 14px; + /*+text-shadow:0px 1px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px 1px #FFFFFF; + -o-text-shadow: 0px 1px 1px #FFFFFF; + text-shadow: 0px 1px 1px #FFFFFF; + padding-bottom: 40px; +} + +.ui-dialog span.message ul { + margin-left: 30px; + margin-top: 14px; + text-align: left; + list-style: disc; +} + +.ui-dialog span.message ul li { + margin-top: 3px; +} + +.ui-dialog span.message p { + text-align: left; + margin-top: 20px; +} + +.ui-dialog-titlebar { + background: #FFFFFF; + color: #000000; + height: 33px; + /*+border-radius:7px 7px 0 0;*/ + -moz-border-radius: 7px 7px 0 0; + -webkit-border-radius: 7px 7px 0 0; + -khtml-border-radius: 7px 7px 0 0; + border-radius: 7px 7px 0 0; + margin: auto; +} + +.ui-dialog-titlebar .ui-icon-closethick { + display: none; +} + +.ui-dialog-title { + /*+placement:shift 8px 9px;*/ + position: relative; + left: 8px; + top: 9px; + font-size: 14px; + padding: 2px 0 5px 30px; + background: url(../images/icons.png) no-repeat 0px -255px; +} + +.notice .ui-dialog-title { + background-position: 0px -288px; +} + +.ui-dialog.confirm .ui-dialog-title { + background: url(../images/icons.png) no-repeat 0px -224px; +} + +.ui-dialog.create-form .ui-dialog-title { + background: url(../images/icons.png) no-repeat 0px -255px; +} + +.ui-dialog.warning .ui-dialog-title { + background: url(../images/icons.png) no-repeat 0px -286px; +} + +.ui-dialog.confirm .ui-button { + /*+placement:shift 0px -8px;*/ + position: relative; + left: 0px; + top: -8px; +} + +/*** Create form*/ +.ui-dialog div.form-container { + width: 94% !important; + height: 106px; + text-align: left; + display: inline-block; +} + +.ui-dialog div.form-container span.message { + text-align: left; + padding: 0 0 23px 5px; + font-size: 15px; +} + +.ui-dialog div.form-container span.message br { + margin-bottom: 13px; +} + +.ui-dialog div.form-container div.form-item { + width: 100%; + display: inline-block; + margin: 0 0 12px; +} + +.ui-dialog div.form-container div.name { + float: left; + clear: both; + width: 115px; + font-size: 15px; + color: #485867; + /*+text-shadow:0px 2px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 2px 1px #FFFFFF; + -webkit-text-shadow: 0px 2px 1px #FFFFFF; + -o-text-shadow: 0px 2px 1px #FFFFFF; + text-shadow: 0px 2px 1px #FFFFFF; + margin: 3px 0 0; +} + +.ui-dialog div.form-container div.name label { + display: block; + width: 119px; + text-align: right; + font-size: 13px; + margin-top: 2px; +} + +.field-required { + color: #EE7B7B; + font-size: 14px; + padding: 0 3px 0 0; + font-weight: bold; +} + +.ui-dialog div.form-container div.value { + width: 61%; + float: left; + margin: 0 0 0 15px; + display: inline-block; +} + +.ui-dialog div.form-container div.value input, +textarea { + width: 98%; + font-size: 14px; + padding: 4px; + background: #F6F6F6; + border: 1px solid #AFAFAF; + float: left; +} + +.ui-dialog div.form-container div.value input.hasDatepicker { + color: #2F5D86; + cursor: pointer; + font-size: 13px; + text-indent: 3px; +} + +.ui-dialog div.form-container div.value input.hasDatepicker:hover { + /*+box-shadow:inset 0px 0px 3px;*/ + -moz-box-shadow: inset 0px 0px 3px; + -webkit-box-shadow: inset 0px 0px 3px; + -o-box-shadow: inset 0px 0px 3px; + box-shadow: inset 0px 0px 3px; +} + +.ui-dialog div.form-container div.value .range-edit { + width: 249px; + height: 33px; + margin: 2px 0 0; +} + +.ui-dialog div.form-container div.value .range-edit .range-item { + width: 124px; + height: 32px; + position: relative; + float: left; +} + +.ui-dialog div.form-container div.value .range-edit input { + width: 105px; + margin: 0 9px 0 0; +} + +.ui-dialog div.form-container div.value .range-edit label.error { + position: absolute; + left: 3px; + top: 25px; +} + +.ui-dialog div.form-container div.value select { + width: 100%; + float: right; +} + +.ui-dialog div.form-container div.value input[type=checkbox] { + width: 14px; +} + +.ui-dialog div.form-container div.value label.error { + display: block; + clear: both; + font-size: 10px; + color: #FA0000; + display: none; +} + +.ui-dialog div.form-container div.multi-array { + display: inline-block; + background: #FFFFFF; + padding: 4px; + /*+border-radius:10px;*/ + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + -khtml-border-radius: 10px; + border-radius: 10px; + border-radius: 10px 10px 10px 10px; + border: 1px solid #808080; + /*+box-shadow:inset 0px 1px 1px #929292;*/ + -moz-box-shadow: inset 0px 1px 1px #929292; + -webkit-box-shadow: inset 0px 1px 1px #929292; + -o-box-shadow: inset 0px 1px 1px #929292; + box-shadow: inset 0px 1px 1px #929292; +} + +.ui-dialog div.form-container div.multi-array .item { + width: 111px; + float: left; + margin: 0 0 13px; +} + +.ui-dialog div.form-container div.multi-array .item .name { + font-size: 11px; + width: 61px; + float: left; +} + +.ui-dialog div.form-container div.multi-array .item .value { + width: 13px; + float: left; +} + +.ui-dialog div.form-container div.multi-array .item .value input { + float: left; + margin: 0; + padding: 0; +} + +.ui-dialog.create-form .ui-button.ok { + margin-left: 0; + float: right; +} + +.ui-dialog.create-form .ui-button.cancel { + background: #B6B6B6 url(../images/gradients.png) 0px -480px; + float: right; + margin-right: 13px; + border: 1px solid #AAAAAA; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; +} + +.ui-dialog.create-form .ui-button.cancel:hover { + background-position: -4px -426px; + border-color: #878787; +} + +/**** Dynamic input*/ +.ui-dialog div.form-container div.value .dynamic-input { + background: #FFFFFF; + width: 98%; + min-height: 50px; + clear: both; + max-height: 211px; + overflow: auto; + overflow-x: hidden; + border: 1px solid #CDCDCD; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; + /*+box-shadow:inset -1px 1px 1px #636363;*/ + -moz-box-shadow: inset -1px 1px 1px #636363; + -webkit-box-shadow: inset -1px 1px 1px #636363; + -o-box-shadow: inset -1px 1px 1px #636363; + box-shadow: inset -1px 1px 1px #636363; +} + +.ui-dialog div.form-container div.value .dynamic-input .form-item { + width: 97%; + margin: 4px 0 0 5px; +} + +.ui-dialog div.form-container div.value .dynamic-input .name { + width: 99px; +} + +.ui-dialog div.form-container div.value .dynamic-input .name label { + width: inherit; + font-size: 12px; +} + +.ui-dialog div.form-container div.value .dynamic-input .value { + width: 40%; +} + +/*User options*/ +#user-options { + background: #FFFFFF; + z-index: 10000; + width: 150px; + position: absolute; + padding: 10px; + top: 20px; + /*+border-radius:0 0 3px 3px;*/ + -moz-border-radius: 0 0 3px 3px; + -webkit-border-radius: 0 0 3px 3px; + -khtml-border-radius: 0 0 3px 3px; + border-radius: 0 0 3px 3px; + /*+box-shadow:0px 1px 7px #000000;*/ + -moz-box-shadow: 0px 1px 7px #000000; + -webkit-box-shadow: 0px 1px 7px #000000; + -o-box-shadow: 0px 1px 7px #000000; + box-shadow: 0px 1px 7px #000000; +} + +#user-options a { + float: left; + width: 100%; + padding: 10px 0; +} + +#user-options a:hover { +} + +/*Dashboard +** Admin*/ +.dashboard.admin { + background: #F2F0F0; + height: 100%; + padding: 10px; + font-size: 13px; + color: #3D5873; +} + +.dashboard.admin .dashboard-container { + background: #FFFFFF; + border: 1px solid #C8C2C2; + /*+border-radius:3px;*/ + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + -khtml-border-radius: 3px; + border-radius: 3px; + padding: 0px 8px 18px 0px; + margin: 0 0 11px; +} + +.dashboard.admin .dashboard-container.sub { + width: 468px; +} + +.dashboard.admin .dashboard-container.sub .button.view-all, +.dashboard.admin .dashboard-container .button.fetch-latest { + font-size: 13px; + float: right; + clear: none; + /*+text-shadow:none;*/ + -moz-text-shadow: none; + -webkit-text-shadow: none; + -o-text-shadow: none; + text-shadow: none; + -moz-text-shadow: 0px 1px 0px #333E49; + -webkit-text-shadow: 0px 1px 0px #333E49; + -o-text-shadow: 0px 1px 0px #333E49; + padding: 3px 8px 3px 10px; + background: rgb(234, 234, 234); + background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2VhZWFlYSIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNkNmQ2ZDYiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+); + background: -moz-linear-gradient(top, rgba(234,234,234,1) 0%, rgba(214,214,214,1) 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(234,234,234,1)), color-stop(100%,rgba(214,214,214,1))); + background: -webkit-linear-gradient(top, rgba(234,234,234,1) 0%,rgba(214,214,214,1) 100%); + background: -o-linear-gradient(top, rgba(234,234,234,1) 0%,rgba(214,214,214,1) 100%); + background: -ms-linear-gradient(top, rgba(234,234,234,1) 0%,rgba(214,214,214,1) 100%); + background: linear-gradient(to bottom, rgba(234,234,234,1) 0%,rgba(214,214,214,1) 100%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eaeaea', endColorstr='#d6d6d6',GradientType=0 ); + border: 1px solid #9D9D9D; + /*+border-radius:3px;*/ + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + -khtml-border-radius: 3px; + border-radius: 3px; + /*+box-shadow:0px 1px #CACACA;*/ + -moz-box-shadow: 0px 1px #CACACA; + -webkit-box-shadow: 0px 1px #CACACA; + -o-box-shadow: 0px 1px #CACACA; + box-shadow: 0px 1px #CACACA; + cursor: pointer; + font-weight: 100; + color: #000000; +} + +.dashboard.admin .dashboard-container.sub .button.view-all:hover, +.dashboard.admin .dashboard-container .button.fetch-latest:hover { + background: #E8E8E8; + /*+box-shadow:inset 0px 0px 6px #636363;*/ + -moz-box-shadow: inset 0px 0px 6px #636363; + -webkit-box-shadow: inset 0px 0px 6px #636363; + -o-box-shadow: inset 0px 0px 6px #636363; + box-shadow: inset 0px 0px 6px #636363; +} + +.dashboard.admin .dashboard-container.sub .title { + float: left; +} + +/**** Head*/ +.dashboard.admin .dashboard-container.head { + width: 966px; + height: 331px; + margin: 9px 0 0; + float: left; +} + +.dashboard.admin .dashboard-container .top { + background: #EFEFEF 0px -4px; + padding: 4px 4px 8px; + width: 100%; + float: left; + margin: 0; + color: #FFFFFF; +} + +.dashboard.admin .dashboard-container .title { + float: left; + font-size: 13px; + font-weight: 100; + /*+text-shadow:0px 1px 1px #9A9A9A;*/ + -moz-text-shadow: 0px 1px 1px #9A9A9A; + -webkit-text-shadow: 0px 1px 1px #9A9A9A; + -o-text-shadow: 0px 1px 1px #9A9A9A; + text-shadow: 0px 1px 1px #9A9A9A; + padding: 5px 0 0 4px; +} + +.dashboard.admin .dashboard-container .title span { + color: #000000; + /*+text-shadow:none;*/ + -moz-text-shadow: none; + -webkit-text-shadow: none; + -o-text-shadow: none; + text-shadow: none; +} + +.dashboard.admin .dashboard-container.head .selects { + float: right; +} + +.dashboard.admin .dashboard-container.head .selects .select { + float: left; + padding: 0; + margin: 0 0 0 21px; +} + +.dashboard.admin .dashboard-container.head .selects .select label { + display: block; + float: left; + padding: 5px 0px 0px; +} + +.dashboard.admin .dashboard-container.head .selects .select select { + width: 124px; + margin: 3px 0 0 10px; + padding: 0px; +} + +/**** Charts / stats*/ +.dashboard.admin .zone-stats { + width: 974px; + height: 316px; + overflow: auto; + overflow-x: hidden; + /*+placement:shift 0px 0px;*/ + position: relative; + left: 0px; + top: 0px; +} + +.dashboard.admin .zone-stats ul { + width: 996px; + /*+placement:shift -2px 11px;*/ + position: relative; + left: -2px; + top: 11px; +} + +.dashboard.admin .zone-stats ul li { + width: 488px; + font-size: 14px; + height: 79px; + float: left; + position: absolute; + position: relative; + cursor: pointer; + z-index: 1; +} + +.dashboard.admin .zone-stats ul li canvas { + position: relative; + z-index: -1; +} + +.dashboard.admin .zone-stats ul li:hover { + background: #FFF2DA; +} + +.dashboard.admin .zone-stats ul li .label { + width: 161px; + float: left; + font-weight: 100; + border-bottom: 1px solid #E2E2E2; + margin: 5px 0 0 22px; + padding: 22px 0 7px; +} + +.dashboard.admin .zone-stats ul li .info { + float: left; + width: 151px; + white-space: nowrap; + margin: 12px 0 0; + color: #636363; +} + +.dashboard.admin .zone-stats ul li .info .name { + font-weight: bold; + margin-top: 8px; + margin-bottom: 9px; + font-size: 12px; + font-weight: 100; + /*[empty]color:;*/ +} + +.dashboard.admin .zone-stats ul li .pie-chart-container { + width: 91px; + height: 69px; + overflow: hidden; + float: left; + position: relative; + /*+placement:shift -8px 7px;*/ + position: relative; + left: -8px; + top: 7px; +} + +.dashboard.admin .zone-stats ul li .pie-chart-container .percent-label { + width: 52px; + color: #C98200; + /*+placement:shift 28px 31px;*/ + position: relative; + left: 28px; + top: 31px; + position: absolute; + font-weight: bold; + text-align: center; +} + +.dashboard.admin .zone-stats ul li .pie-chart { + width: 70px; + height: 66px; + float: left; + margin: 3px 27px 0 16px; + position: relative; + z-index: -1; +} + +.dashboard.admin .dashboard-container .stats ul li { + display: block; + width: 97%; + height: 40px; + background: url(../images/bg-gradients.png) 0px -29px; + clear: both; + border: 1px solid #C8C2C2; + /*+border-radius:10px;*/ + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + -khtml-border-radius: 10px; + border-radius: 10px; + border-radius: 10px 10px 10px 10px; + padding: 0 12px 0px; + margin: 0 0 10px; +} + +.dashboard.admin .dashboard-container .stats ul li .name { + width: 178px; + float: left; + font-size: 11px; + font-weight: bold; + margin: 15px 15px 0 0; +} + +.dashboard.admin .dashboard-container .stats ul li div.value { + float: left; + background: url(../images/bg-gradients.png) 0px -51px; + width: 295px; + height: 100%; + border-left: 1px solid #C8C2C2; + border-right: 1px solid #C8C2C2; + margin: 0 9px 0 0; +} + +.dashboard.admin .dashboard-container .stats ul li .value .content { + background: url(../images/bg-gradients.png) repeat-x 0px 0px; + margin: 6px 9px 9px; + padding: 9px; + color: #FFFFFF; + /*Adjusting the font size for proper display*/ + font-size: 10px; + border-left: 1px solid #6A6A6A; + border-right: 1px solid #6A6A6A; + border-bottom: 1px solid #FFFFFF; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; + /*+text-shadow:0px -1px 1px #6F6F6F;*/ + -moz-text-shadow: 0px -1px 1px #6F6F6F; + -webkit-text-shadow: 0px -1px 1px #6F6F6F; + -o-text-shadow: 0px -1px 1px #6F6F6F; + text-shadow: 0px -1px 1px #6F6F6F; +} + +.dashboard.admin .dashboard-container .stats ul li .chart { + float: left; + width: 290px; + height: 17px; + padding: 0px 1px; + background: url(../images/bg-gradients.png) 0px -130px; + margin: 12px 23px 0 0; + /*+border-radius:7px;*/ + -moz-border-radius: 7px; + -webkit-border-radius: 7px; + -khtml-border-radius: 7px; + border-radius: 7px; + border-radius: 7px 7px 7px 7px; + border-bottom: 1px solid #FFFFFF; + border-top: 1px solid #727272; +} + +.dashboard.admin .dashboard-container .stats ul li .chart .chart-line { + height: 15px; + background: url(../images/bg-gradients.png) 0px -149px; + /*+border-radius:10px;*/ + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + -khtml-border-radius: 10px; + border-radius: 10px; + border-radius: 10px 10px 10px 10px; + margin: 1px 0px 0; +} + +.dashboard.admin .dashboard-container .stats ul li .percentage { + float: left; + font-size: 20px; + font-weight: bold; + margin: 13px 0 0; + /*+text-shadow:0px -2px 1px #FFFFFF;*/ + -moz-text-shadow: 0px -2px 1px #FFFFFF; + -webkit-text-shadow: 0px -2px 1px #FFFFFF; + -o-text-shadow: 0px -2px 1px #FFFFFF; + text-shadow: 0px -2px 1px #FFFFFF; +} + +/**** Alerts*/ +.dashboard.admin .dashboard-container.sub.alerts { + float: left; + margin: 0 12px 0 0; + height: 270px; + overflow: hidden; + position: relative; +} + +.dashboard.admin .dashboard-container.sub.alerts.last { + margin-right: 0; +} + +.dashboard.admin .dashboard-container.sub.alerts ul { + width: 468px; + height: 234px; + overflow: auto; + overflow-x: hidden; + position: relative; + margin: 0px 0 0 8px; +} + +.dashboard.admin .dashboard-container.sub.alerts ul li { + background: #F0F0F0; + float: left; + border: 1px solid #D4D0D0; + /*+border-radius:3px;*/ + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + -khtml-border-radius: 3px; + border-radius: 3px; + margin: 9px; + padding: 8px; +} + +.dashboard.admin .dashboard-container.sub.alerts ul li .content { +} + +.dashboard.admin .dashboard-container.sub.alerts ul li { + border: 1px solid #FF7070; + background: #FFEFEF; +} + +.dashboard.admin .dashboard-container.sub.alerts ul li span.title { + width: 100%; + font-weight: bold; + font-size: 14px; + font-weight: 100; + color: #266E9A; + margin: 3px 0 5px; + /*+text-shadow:0px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px #FFFFFF; + -o-text-shadow: 0px 1px #FFFFFF; + text-shadow: 0px 1px #FFFFFF; + padding: 0; +} + +.dashboard.admin .dashboard-container.sub.alerts ul li p { + float: left; + margin: 4px 0px 0px; + color: #252525; +} + +.dashboard.admin .dashboard-container.sub.alerts ul li p br { + display: none; +} + +/*** User*/ +.dashboard.user { +} + +#browser div.panel .dashboard.user .toolbar { + height: 60px; + position: relative; +} + +.dashboard.user .button.view-all { + float: right; + border: 1px solid #4B5B6B; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; + color: #FFFFFF; + /*+text-shadow:0px -1px 2px #13293E;*/ + -moz-text-shadow: 0px -1px 2px #13293E; + -webkit-text-shadow: 0px -1px 2px #13293E; + -o-text-shadow: 0px -1px 2px #13293E; + text-shadow: 0px -1px 2px #13293E; + padding: 2px 3px 3px; + margin: -4px 4px -4px 0; + text-indent: 0; + background: url(../images/bg-gradients.png) 0px -147px; +} + +.dashboard.user .button.view-all:hover { + background-position: 0px 0px; + /*+text-shadow:0px 1px 1px #000000;*/ + -moz-text-shadow: 0px 1px 1px #000000; + -webkit-text-shadow: 0px 1px 1px #000000; + -o-text-shadow: 0px 1px 1px #000000; + text-shadow: 0px 1px 1px #000000; +} + +/**** Actions*/ +.dashboard.user .dashboard-actions { +} + +.dashboard.user .dashboard-actions ul { + padding: 11px; +} + +.dashboard.user .dashboard-actions ul li { + float: left; + width: 123px; + cursor: pointer; + height: 40px; + margin: 0 9px 0 0; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; + /*+box-shadow:inset 0px 0px 1px #DDE3EC;*/ + -moz-box-shadow: inset 0px 0px 1px #DDE3EC; + -webkit-box-shadow: inset 0px 0px 1px #DDE3EC; + -o-box-shadow: inset 0px 0px 1px #DDE3EC; + box-shadow: inset 0px 0px 1px #DDE3EC; + border: 1px solid #395268; + border-right: 1px solid #556778; + background: url(../images/bg-gradients.png) repeat-x 0px -181px; +} + +.dashboard.user .dashboard-actions ul li span { + color: #FFFFFF; + font-size: 11px; + /*+text-shadow:0px 1px 2px #444444;*/ + -moz-text-shadow: 0px 1px 2px #444444; + -webkit-text-shadow: 0px 1px 2px #444444; + -o-text-shadow: 0px 1px 2px #444444; + text-shadow: 0px 1px 2px #444444; + background: url(../images/icons.png) no-repeat -613px -309px; + padding: 8px 12px 11px 34px; + /*+placement:shift 4px 10px;*/ + position: relative; + left: 4px; + top: 10px; +} + +.dashboard.user .dashboard-actions ul li.add-iso span { + background-position: -613px -352px; +} + +.dashboard.user .dashboard-actions ul li.add-volume span { + background-position: -613px -264px; +} + +.dashboard.user .dashboard-actions ul li.acquire-ip span { + background-position: -613px -389px; +} + +/**** VM Status*/ +.dashboard.user .vm-status { + width: 98%; + margin: 19px auto auto; + border: 1px solid #D2CDCD; + /*+border-radius:9px;*/ + -moz-border-radius: 9px; + -webkit-border-radius: 9px; + -khtml-border-radius: 9px; + border-radius: 9px; + border-radius: 9px 9px 9px 9px; +} + +.dashboard.user .vm-status .title { + width: 100%; + background: url(../images/bg-gradients.png) 0px -53px; + /*+border-radius:10px 10px 0 0;*/ + -moz-border-radius: 10px 10px 0 0; + -webkit-border-radius: 10px 10px 0 0; + -khtml-border-radius: 10px 10px 0 0; + border-radius: 10px 10px 0 0; + font-size: 13px; + color: #4A5967; + padding: 13px 0 12px; + border-bottom: 1px solid #C8C2C2; + /*+text-shadow:0px 1px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px 1px #FFFFFF; + -o-text-shadow: 0px 1px 1px #FFFFFF; + text-shadow: 0px 1px 1px #FFFFFF; +} + +.dashboard.user .vm-status .title span { + padding: 0 0 0 8px; +} + +.dashboard.user .vm-status .content { + padding: 9px 0px; +} + +.dashboard.user .vm-status .content ul { + display: inline-block; + margin: auto; +} + +.dashboard.user .vm-status .content ul li { + float: left; + width: 243px; + height: 237px; + border: 1px solid #E6EBEE; + border-top: 2px solid #D3D9DE; + /*+border-radius:10px;*/ + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + -khtml-border-radius: 10px; + border-radius: 10px; + border-radius: 10px 10px 10px 10px; + margin: 0 0 0 11px; + background: #EBEDF1; +} + +.dashboard.user .vm-status .content ul li .name { + font-size: 28px; + color: #5C7082; + padding: 0 0 0 35px; + margin: 14px 0 0; + /*+text-shadow:0px 2px 2px #FFFFFF;*/ + -moz-text-shadow: 0px 2px 2px #FFFFFF; + -webkit-text-shadow: 0px 2px 2px #FFFFFF; + -o-text-shadow: 0px 2px 2px #FFFFFF; + text-shadow: 0px 2px 2px #FFFFFF; + background: url(../images/icons.png) -617px -488px; +} + +.dashboard.user .vm-status .content ul li .value { + background: #DFE9CC; + width: 229px; + color: #5D7C98; + /*+text-shadow:0px 1px 2px #FFFFFF;*/ + -moz-text-shadow: 0px 1px 2px #FFFFFF; + -webkit-text-shadow: 0px 1px 2px #FFFFFF; + -o-text-shadow: 0px 1px 2px #FFFFFF; + text-shadow: 0px 1px 2px #FFFFFF; + font-size: 58px; + margin: 12px auto auto; + text-align: center; + padding: 59px 0px; +} + +.dashboard.user .vm-status .content ul li.stopped .name { + background-position: -617px -524px; +} + +.dashboard.user .vm-status .content ul li.stopped .value { + background: #EDCBCE; +} + +.dashboard.user .vm-status .content ul li.total .name { + background-position: -617px -559px; +} + +.dashboard.user .vm-status .content ul li.total .value { + background: #DFE4E9; +} + +/***** Tables / status list*/ +.dashboard.user .status-lists { + margin: 15px 0 0 8px; +} + +.dashboard.user .status-lists ul li.events { + width: 512px; +} + +.dashboard.user .status-lists ul li.events .content li { + width: 97%; + cursor: pointer; + margin: 6px 11px 0 0; + font-size: 11px; + padding: 13px 0 12px 16px; + color: #495A76; + /*+text-shadow:0px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px #FFFFFF; + -o-text-shadow: 0px 1px #FFFFFF; + text-shadow: 0px 1px #FFFFFF; + border: 1px solid #DBDBDB; + /*+box-shadow:0px 2px 4px #C0C0C0;*/ + -moz-box-shadow: 0px 2px 4px #C0C0C0; + -webkit-box-shadow: 0px 2px 4px #C0C0C0; + -o-box-shadow: 0px 2px 4px #C0C0C0; + box-shadow: 0px 2px 4px #C0C0C0; + background: #EFEFEF url(../images/bg-gradients.png) repeat-x 0px -29px; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; +} + +.dashboard.user .status-lists ul li.events .content li .title { + font-weight: bold; + color: #4A5A6A; + margin-bottom: 9px; +} + +.dashboard.user .status-lists ul li { + float: left; + margin: 0 10px 0 0; +} + +.dashboard.user .status-lists table { + width: 252px; + margin: 0; + cursor: default; +} + +.dashboard.user .status-lists table th { + padding: 8px 0px 6px; +} + +.dashboard.user .status-lists .events table { + width: 515px; +} + +.dashboard.user .status-lists table tbody { + height: 256px; + display: block; + overflow: auto; + overflow-x: hidden; + padding: 0 0px; +} + +.dashboard.user .status-lists table td.value { + cursor: default; +} + +.dashboard.user .status-lists table td.desc { + width: 151px; + overflow: hidden; + cursor: default; +} + +.dashboard.user .status-lists .my-account table tbody tr { +} + +.dashboard.user .status-lists .my-account table tbody tr td { + padding-top: 19px; + padding-bottom: 19px; +} + +.dashboard.user .status-lists table thead { + background: url(../images/bg-gradients.png) 0px -351px; + border-top: 1px solid #C4C5C5; +} + +.dashboard.user .status-lists table tr.odd { + background: #DFE1E3; +} + +.dashboard.user .status-lists table td { + vertical-align: middle; +} + +/****** IP addresses*/ +.dashboard.user .status-lists li.ip-addresses { +} + +.dashboard.user .status-lists li.ip-addresses td { + width: 250px; + padding: 28px 0 51px; +} + +.dashboard.user .status-lists li.ip-addresses .desc { + color: #3F5468; +} + +.dashboard.user .status-lists li.ip-addresses .value { + font-size: 29px; + margin: 7px 0 0; + /*+text-shadow:0px 1px 2px #FFFFFF;*/ + -moz-text-shadow: 0px 1px 2px #FFFFFF; + -webkit-text-shadow: 0px 1px 2px #FFFFFF; + -o-text-shadow: 0px 1px 2px #FFFFFF; + text-shadow: 0px 1px 2px #FFFFFF; +} + +/*System chart*/ +.system-chart { + width: 100%; + height: 100%; + overflow: auto; + overflow-x: hidden; +} + +.system-chart ul.resources li { + background: transparent url(../images/bg-gradients.png) repeat-x 0px -1340px; + text-align: left; + width: 100px; + height: 60px; + position: absolute; + border: 1px solid #99A0A5; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; + /*+box-shadow:0px 0px 2px #A6A6A6;*/ + -moz-box-shadow: 0px 0px 2px #A6A6A6; + -webkit-box-shadow: 0px 0px 2px #A6A6A6; + -o-box-shadow: 0px 0px 2px #A6A6A6; + box-shadow: 0px 0px 2px #A6A6A6; +} + +.system-chart ul.resources li .button.view-all { + width: 65px; + height: 25px; + background: url(../images/buttons.png) no-repeat -458px -504px; + /*+placement:shift 32px 34px;*/ + position: relative; + left: 32px; + top: 34px; + position: absolute; +} + +.system-chart ul.resources li .button.view-all:hover { + background-position: -537px -504px; +} + +.system-chart ul.resources li .button.view-all .view-all-label { + color: #FFFFFF; + font-size: 11px; + /*+text-shadow:0px -1px 1px #000000;*/ + -moz-text-shadow: 0px -1px 1px #000000; + -webkit-text-shadow: 0px -1px 1px #000000; + -o-text-shadow: 0px -1px 1px #000000; + text-shadow: 0px -1px 1px #000000; + /*+placement:shift 9px 0px;*/ + position: relative; + left: 9px; + top: 0px; +} + +.system-chart ul.resources li .label { + font-weight: bold; + font-size: 12px; + color: #5C7485; + /*+text-shadow:0px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px #FFFFFF; + -o-text-shadow: 0px 1px #FFFFFF; + text-shadow: 0px 1px #FFFFFF; + /*+placement:shift 3px 3px;*/ + position: relative; + left: 3px; + top: 3px; + position: absolute; +} + +/** Resources*/ +.system-chart.dashboard.admin { + width: 97%; + height: 96%; + background: transparent; +} + +.system-chart.dashboard.admin .dashboard-container { + width: 930px; + border: none; +} + +.system-chart.dashboard.admin .dashboard-container .top { + background: transparent; +} + +.system-chart.dashboard.admin .dashboard-container .top .title { + color: #55687A; +} + +.system-chart.dashboard.admin .dashboard-container .top .title span { + /*+text-shadow:0px 0px #FFFFFF;*/ + -moz-text-shadow: 0px 0px #FFFFFF; + -webkit-text-shadow: 0px 0px #FFFFFF; + -o-text-shadow: 0px 0px #FFFFFF; + text-shadow: 0px 0px #FFFFFF; +} + +.system-chart.dashboard.admin .dashboard-container .stats .chart { + width: 300px; +} + +/** Compute*/ +.system-chart.compute { + background: url(../images/bg-system-chart-compute.png) no-repeat center; +} + +/*** Compute resources*/ +.system-chart.compute ul.resources { + width: 98%; + height: 97%; + margin: 0; + position: relative; +} + +.system-chart.compute ul.resources li.zone { + left: 196px; +} + +.system-chart.compute ul.resources li.zone .label { + width: 100%; + left: 0px; + top: 20px; + font-size: 14px; + text-align: center; +} + +.system-chart.compute ul.resources li.pods { + left: 299px; + top: 112px; +} + +.system-chart.compute ul.resources li.clusters { + left: 396px; + top: 189px; +} + +.system-chart.compute ul.resources li.hosts { + left: 507px; + top: 265px; +} + +.system-chart.compute ul.resources li.primaryStorage { + left: 507px; + top: 375px; +} + +.system-chart.compute ul.resources li.secondaryStorage { + left: 299px; + top: 497px; +} + +/** Network*/ +.system-chart.network { +} + +.system-chart.network .network-switch-icon { + color: #506980; + font-weight: bold; + background: url(../images/bg-gradients.png) repeat-x 0px -38px; + border: 1px solid #CDCDCD; + border-top: 2px solid #FFFFFF; + /*+box-shadow:0px 0px 2px #A4A4A4;*/ + -moz-box-shadow: 0px 0px 2px #A4A4A4; + -webkit-box-shadow: 0px 0px 2px #A4A4A4; + -o-box-shadow: 0px 0px 2px #A4A4A4; + box-shadow: 0px 0px 2px #A4A4A4; + /*+border-radius:2px;*/ + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + -khtml-border-radius: 2px; + border-radius: 2px; + border-radius: 2px 2px 2px 2px; + padding: 8px 7px; + /*+placement:shift 187px 76px;*/ + position: relative; + left: 187px; + top: 76px; + position: absolute; + z-index: 3; +} + +.system-chart.network .base-circle-icon { + width: 35px; + height: 34px; + background: url(../images/bg-system-network-traffic.png) 0px -853px; + /*+placement:shift 227px 557px;*/ + position: relative; + left: 227px; + top: 557px; + position: absolute; + z-index: 5; +} + +.system-chart.network ul.resources { + width: 100%; + height: 98%; + position: absolute; + z-index: 2; +} + +.system-chart.network ul.resources li { + background: transparent; + /*+box-shadow:0px 0px;*/ + -moz-box-shadow: 0px 0px; + -webkit-box-shadow: 0px 0px; + -o-box-shadow: 0px 0px; + box-shadow: 0px 0px; + -moz-box-shadow: 0px 0px none; + -webkit-box-shadow: 0px 0px none; + -o-box-shadow: 0px 0px none; + -moz-box-shadow: none; + -webkit-box-shadow: none; + -o-box-shadow: none; + border: none; + /*+border-radius:0px;*/ + -moz-border-radius: 0px; + -webkit-border-radius: 0px; + -khtml-border-radius: 0px; + border-radius: 0px; + border-radius: 0px 0px 0px 0px; +} + +.system-chart.network ul.resources li .view-all { + /*+placement:shift 19px 21px;*/ + position: relative; + left: 19px; + top: 21px; +} + +.system-chart.network ul.resources li.public { + /*+placement:shift 356px 23px;*/ + position: relative; + left: 356px; + top: 23px; + position: absolute; +} + +.system-chart.network ul.resources li.guest { + /*+placement:shift 356px 155px;*/ + position: relative; + left: 356px; + top: 155px; + position: absolute; +} + +.system-chart.network ul.resources li.management { + /*+placement:shift 356px 242px;*/ + position: relative; + left: 356px; + top: 242px; + position: absolute; +} + +.system-chart.network ul.resources li.storage { + /*+placement:shift 356px 333px;*/ + position: relative; + left: 356px; + top: 333px; + position: absolute; +} + +.system-chart.network ul.resources li.providers { + /*+placement:shift 248px 427px;*/ + position: relative; + left: 248px; + top: 427px; + height: 77px; + width: 258px; + position: absolute; + background: url(../images/bg-system-network-traffic.png) no-repeat -50px -848px; +} + +.system-chart.network ul.resources li.providers span { + /*+placement:shift 99px 5px;*/ + position: relative; + left: 99px; + top: 5px; + position: absolute; + font-size: 12px; +} + +.system-chart.network ul.resources li.providers .view-all { + /*+placement:shift 186px 48px;*/ + position: relative; + left: 186px; + top: 48px; + position: absolute; +} + +.system-chart.network .system-network-chart { + width: 100%; + height: 100%; + position: relative; + z-index: 1; +} + +.system-chart.network .system-network-chart .network-chart-item { + background: url(../images/bg-system-network-traffic.png) no-repeat; + width: 213px; + height: 539px; +} + +.system-chart.network .system-network-chart .network-chart-item.public { + background-position: -793px -1px; + /*+placement:shift 245px 20px;*/ + position: relative; + left: 245px; + top: 20px; + position: absolute; +} + +.system-chart.network .system-network-chart .network-chart-item.management { + background-position: -273px 12px; + /*+placement:shift 239px 20px;*/ + position: relative; + left: 239px; + top: 20px; + position: absolute; +} + +.system-chart.network .system-network-chart .network-chart-item.storage { + background-position: -523px 12px; + /*+placement:shift 231px 20px;*/ + position: relative; + left: 231px; + top: 20px; + position: absolute; +} + +.system-chart.network .system-network-chart .network-chart-item.guest { + background-position: -43px 12px; + /*+placement:shift 251px 20px;*/ + position: relative; + left: 251px; + top: 20px; + position: absolute; +} + +/** NAAS +** Add initial resource form*/ +.panel .add-first-network-resource { + margin: 37px; + font-size: 14px; + background: #FFFFFF; + padding: 19px 19px 79px; + /*+border-radius:12px;*/ + -moz-border-radius: 12px; + -webkit-border-radius: 12px; + -khtml-border-radius: 12px; + border-radius: 12px; + border-radius: 12px 12px 12px 12px; + border: 1px solid #ECECEC; +} + +.panel .add-first-network-resource form { + display: inline-block; + height: 442px; + overflow: auto; + overflow-x: hidden; +} + +.panel .add-first-network-resource .title { + font-size: 26px; + color: #3984D1; + /*+text-shadow:0px 1px 2px #BCBCBC;*/ + -moz-text-shadow: 0px 1px 2px #BCBCBC; + -webkit-text-shadow: 0px 1px 2px #BCBCBC; + -o-text-shadow: 0px 1px 2px #BCBCBC; + text-shadow: 0px 1px 2px #BCBCBC; + margin: 0 0 17px; +} + +.panel .add-first-network-resource .message { + display: block; + color: #545151; + margin: 0 0 30px; +} + +.panel .add-first-network-resource .form-item { + width: 409px; + display: inline-block; + padding: 5px; + position: relative; + margin: 0 0 2px; +} + +.panel .add-first-network-resource .form-item label { + float: left; + width: 109px; + text-align: right; +} + +.panel .add-first-network-resource .form-item label.error { + display: none; + font-size: 10px; + position: absolute; + top: 30px; + left: 137px; +} + +.panel .add-first-network-resource .form-item input { + float: right; + /*+border-radius:6px;*/ + -moz-border-radius: 6px; + -webkit-border-radius: 6px; + -khtml-border-radius: 6px; + border-radius: 6px; + border-radius: 6px 6px 6px 6px; + font-size: 16px; + border: 1px solid #B7B7B7; +} + +.panel .add-first-network-resource .form-item input[type=checkbox] { + float: right; + margin: 0 266px 0 0; +} + +.panel .add-first-network-resource .form-item input[type=text], +.panel .add-first-network-resource .form-item input[type=password], +.panel .add-first-network-resource .form-item input[type=text], +.panel .add-first-network-resource .form-item select { + width: 276px; +} + +.panel .add-first-network-resource .form-item select { + width: 280px; + margin: 0 0 0 20px; +} + +.panel .add-first-network-resource .button { + padding: 11px 23px 11px 21px; + cursor: pointer; + background: url(../images/bg-gradients.png) repeat-x 0px -221px; + margin: 29px 0 0; + /*+border-radius:10px;*/ + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + -khtml-border-radius: 10px; + border-radius: 10px; + border-radius: 10px 10px 10px 10px; + border: 1px solid #858585; + color: #FFFFFF; + clear: both; + /*[empty]position:;*/ +} + +.panel .add-first-network-resource .multi-array { + background: #FFFFFF; + border: 1px solid #DCDCDC; + display: inline-block; + float: left; + padding: 12px; + clear: both; + width: 383px; + margin: 3px 0 10px; + /*+border-radius:7px;*/ + -moz-border-radius: 7px; + -webkit-border-radius: 7px; + -khtml-border-radius: 7px; + border-radius: 7px; + border-radius: 7px 7px 7px 7px; +} + +.panel .add-first-network-resource .multi-array .item { + max-width: 155px; + float: left; + margin: 6px 0 0 24px; +} + +.panel .add-first-network-resource .multi-array .item .name { + float: left; +} + +.panel .add-first-network-resource .multi-array .item .value { + float: right; + margin: 0 0 0 13px; +} + +.panel .add-first-network-resource .multi-array .item .value input { + margin: 0; +} + +/*Form validation*/ +input.error { + background: #FEE5E5; +} + +label.error { + color: #FF0000; +} + +/*Multi-step wizard*/ +.multi-wizard { + width: 500px; + height: 550px; + display: inline-block; +} + +/*** Progress bar*/ +.multi-wizard .progress { + color: #FFFFFF; + font-size: 11px; +} + +.multi-wizard .progress ul { + width: 900px; + height: 40px; + float: left; + clear: both; +} + +/*[clearfix]*/.multi-wizard .progress ul li { + float: left; + width: 128px; + height: 40px; + padding: 0 0px; + position: relative; +} + +.multi-wizard.instance-wizard .progress ul li { + width: 102px; + margin-left: 8px; +} + +.multi-wizard .progress ul li.first { + /*+border-radius:5px;*/ + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + -khtml-border-radius: 5px; + border-radius: 5px; +} + +.multi-wizard .progress ul li.last { +} + +.multi-wizard .progress ul li.active { + background: url(../images/bg-gradients.png) 0px -221px; + height: 40px; + /*+border-radius:5px;*/ + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + -khtml-border-radius: 5px; + border-radius: 5px; +} + +.multi-wizard .progress ul li span { + display: block; + /*+placement:shift 46px 17px;*/ + position: relative; + left: 46px; + top: 17px; + text-align: left; + color: #000000; + /*+text-shadow:0px 1px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px 1px #FFFFFF; + -o-text-shadow: 0px 1px 1px #FFFFFF; + text-shadow: 0px 1px 1px #FFFFFF; + float: right; + position: absolute; + width: 62px; + text-align: center; +} + +.multi-wizard.instance-wizard .progress ul li span { + left: 26px; + top: 16px; +} + +.multi-wizard .progress ul li span.multiline { + width: 71px; + top: 12px; +} + +.multi-wizard .progress ul li span.arrow { + width: 17px; + height: 15px; + /*+placement:displace 74px -3px;*/ + position: absolute; + margin-left: 74px; + margin-top: -3px; + background: url(../images/icons.png) no-repeat 0px -422px; + z-index: 1000; + display: none; +} + +.multi-wizard.instance-wizard .progress ul li span.arrow { + left: 19px; +} + +.multi-wizard .progress ul li.active span.arrow { + background-position: -1px -396px; +} + +.multi-wizard .progress ul li span.number { + width: auto; + position: absolute; + top: 7px; + left: 26px; + font-size: 27px; + font-weight: bold; + color: #BBBBBB; + background: transparent; +} + +.multi-wizard.instance-wizard .progress ul li span.number { + left: 8px; +} + +.multi-wizard.instance-wizard .progress ul li span.multiline { + width: 79px; + left: 26px; +} + +.multi-wizard .progress ul li.active span { + /*+text-shadow:0px -1px 1px #004AFF;*/ + -moz-text-shadow: 0px -1px 1px #004AFF; + -webkit-text-shadow: 0px -1px 1px #004AFF; + -o-text-shadow: 0px -1px 1px #004AFF; + text-shadow: 0px -1px 1px #004AFF; + color: #FFFFFF; +} + +.multi-wizard .progress ul li.active span.number { +} + +/*** Content*/ +.multi-wizard .main-desc { + font-size: 11px; + text-align: left; + width: 437px; + /*+placement:shift 3px 25px;*/ + position: relative; + left: 3px; + top: 25px; + line-height: 17px; +} + +.multi-wizard .content { + background: #FFFFFF; + width: 440px; + min-height: 366px; + margin: 24px 0 0; + padding-bottom: 8px; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; + display: inline-block; + float: left; +} + +.multi-wizard .select-security-group .content { + height: 366px; +} + +.multi-wizard .content .section { + width: 416px; + background: #E9E9E9; + margin: 16px auto auto; + overflow: hidden; + text-align: left; + font-size: 12px; + color: #505A62; + border: 1px solid #E0DFDF; +} + +.multi-wizard.instance-wizard .service-offering .content { + width: 463px; + max-height: 365px; + overflow: auto; + overflow-x: hidden; +} + +.multi-wizard .content .section .select-area { + width: 334px; + height: 45px; + margin: 9px auto auto; + background: #D6D6D6; +} + +.multi-wizard .content .section .select-area .desc { + text-align: left; + width: 155px; + float: right; + padding: 9px 0 0; + font-size: 12px; + color: #989898; +} + +.multi-wizard .content .section .select-area input { + float: left; + margin: 0; + padding: 9px; +} + +.multi-wizard .content .section .select-area select { + padding: 0; + width: 158px; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; + margin: 11px 0px 0 14px; + float: left; +} + +.multi-wizard .content .section .select-area input[type=radio] { + margin: 14px 16px 0; +} + +.multi-wizard .content .section .select-area label { + float: left; + text-align: left; + font-size: 18px; + color: #62798E; + /*+text-shadow:0px 2px 2px #EFEFEF;*/ + -moz-text-shadow: 0px 2px 2px #EFEFEF; + -webkit-text-shadow: 0px 2px 2px #EFEFEF; + -o-text-shadow: 0px 2px 2px #EFEFEF; + text-shadow: 0px 2px 2px #EFEFEF; + margin: 12px 12px 12px 2px; +} + +.multi-wizard .content .section .select-area label.error { + font-size: 10px; + color: #FF0000; + margin: 2px 0 0 14px; +} + +.multi-wizard .content .section p { + font-size: 11px; + text-align: left; + color: #808080; + padding: 0 0 0 40px; +} + +.multi-wizard .content .section h3 { + text-align: left; + color: #62798E; + font-weight: bold; + padding: 14px 14px 3px 39px; + /*+text-shadow:0px 1px 1px #EFEFEF;*/ + -moz-text-shadow: 0px 1px 1px #EFEFEF; + -webkit-text-shadow: 0px 1px 1px #EFEFEF; + -o-text-shadow: 0px 1px 1px #EFEFEF; + text-shadow: 0px 1px 1px #EFEFEF; + margin: 0; +} + +.multi-wizard .content .section.select-zone { + height: 117px; +} + +.multi-wizard .content .section.select-template { + height: 206px; +} + +.multi-wizard .content.tab-view { + margin: 31px 0px 0px; + background: transparent; + padding: 0px 8px; +} + +.multi-wizard .content.tab-view div.ui-tabs-panel { + border: 1px solid #E2DDDD; + clear: both; + height: 72% !important; + width: 95%; + margin: auto; + overflow: auto; + overflow-x: hidden; + height: 591px; + /*+border-radius:0 3px 10px 10px;*/ + -moz-border-radius: 0 3px 10px 10px; + -webkit-border-radius: 0 3px 10px 10px; + -khtml-border-radius: 0 3px 10px 10px; + border-radius: 0 3px 10px 10px; + background: #FFFFFF; +} + +.multi-wizard .content.tab-view div.ui-tabs-panel.ui-tabs-hide { + display: none; +} + +.multi-wizard.instance-wizard .select-iso .content .select .hypervisor { + float: left; + display: block; + clear: both; + margin: 12px 0 0 58px; + /*+placement:shift 0px -6px;*/ + position: relative; + left: 0px; + top: -6px; +} + +.multi-wizard.instance-wizard .select-iso .content .select .hypervisor select { + width: 160px; +} + +.multi-wizard.instance-wizard .select-iso .content .select .hypervisor label { + color: #000000; + font-size: 11px; + margin-right: 9px; + margin-left: 2px; + /*+placement:shift;*/ + position: relative; + left: 0; + top: 0; +} + +.multi-wizard.instance-wizard .select-iso .wizard-step-conditional.select-iso .content .select.selected { + height: 90px; +} + +/*** UI widgets*/ +.multi-wizard .ui-tabs ul.ui-tabs-nav { + margin-left: 2px; + margin-top: 7px; + display: block; + height: 41px; + background: transparent; + border: none; + /*+placement:shift -6px 5px;*/ + position: relative; + left: -6px; + top: 5px; + overflow: hidden; + display: inline-block; + z-index: 10; +} + +.multi-wizard .select-iso .ui-tabs ul { + float: left; + left: 0px; + top: 1px; +} + +.multi-wizard .ui-tabs ul li.first a { + border-left: 1px solid #E2DDDD; +} + +.multi-wizard .ui-tabs ul li.last a { + border-right: 1px solid #E2DDDD; +} + +.multi-wizard .ui-tabs li.ui-state-default a { + float: left; + text-align: center; + font-size: 11px; + margin-right: 1px; + color: #4E6070; + text-decoration: none; + background: #DEE3E5; + padding-left: 0; + padding-right: 0; +} + +.multi-wizard .ui-tabs li.ui-state-active a { + background: #FFFFFF; +} + +.multi-wizard .ui-slider { + width: 136px; + padding: 0; + margin: 8px -2px 3px; + background: url(../images/bg-gradients.png) 0px -307px; + float: left; +} + +.multi-wizard .ui-slider .ui-slider-handle { + display: block; + background: url(../images/buttons.png) -622px -274px; + width: 18px; + height: 18px; + position: relative; + outline: none; +} + +/*** Select container*/ +.multi-wizard .select-container { + height: 352px; + overflow: auto; + overflow-x: hidden; + border: 1px solid #D9DFE1; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; + margin: 10px 10px 0px; +} + +.multi-wizard .select-container p { + padding: 11px; + color: #424242; + background: #DFDFDF; +} + +.multi-wizard .select-container .select { + font-size: 13px; + margin: -1px 0 0; + padding: 0 0 14px; + min-height: 35px; + width: 100%; + display: inline-block; + text-align: left; + float: left; + border: none; + background: #FFFFFF; +} + +.multi-wizard .select-container .select.odd { + background: #EBEFF4; +} + +.multi-wizard .select-container .select input { + float: left; + margin: 24px 24px 0; +} + +.multi-wizard .select-container .select .select-desc { + max-width: 335px; + min-height: 28px; + overflow: hidden; + float: left; + clear: none; + margin: 21px 0 0; + display: inline-block; +} + +.multi-wizard .select-container .select .select-desc .name { + font-weight: bold; + margin: 0 0 5px; +} + +.multi-wizard .select-container .select .select-desc .desc { + font-size: 11px; + color: #808080; + display: inline-block; + /*[empty]height:;*/ +} + +/*** Buttons*/ +.multi-wizard .buttons { + width: 100%; + position: absolute; + bottom: 10px; + left: 0px; +} + +.multi-wizard .buttons .button { + width: 88px; + height: 16px; + padding: 11px 0 8px; + /*+box-shadow:0px 1px 1px #FFFFFF;*/ + -moz-box-shadow: 0px 1px 1px #FFFFFF; + -webkit-box-shadow: 0px 1px 1px #FFFFFF; + -o-box-shadow: 0px 1px 1px #FFFFFF; + box-shadow: 0px 1px 1px #FFFFFF; + border: 1px solid #78818F; + cursor: pointer; + font-weight: bold; + font-size: 12px; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; +} + +.multi-wizard .buttons .button.next { + /*+placement:float-right 77px 0px;*/ + float: right; + position: relative; + left: 77px; + top: 0px; + color: #FFFFFF; + /*+text-shadow:0px -1px 1px #465259;*/ + -moz-text-shadow: 0px -1px 1px #465259; + -webkit-text-shadow: 0px -1px 1px #465259; + -o-text-shadow: 0px -1px 1px #465259; + text-shadow: 0px -1px 1px #465259; + background: #0049FF url(../images/gradients.png) 0px -317px; + font-weight: bold; + border: 1px solid #0069CF; + border-top: 1px solid #0070FC; +} + +.multi-wizard .buttons .button.next:hover { + background-position: -3px -368px; + border: 1px solid #0035B8; + border-bottom: 1px solid #0062FA; +} + +.multi-wizard .buttons .button.next.final { + padding: 4px 0px 9px; + width: 115px; + margin: 3px 0 0; +} + +.multi-wizard .buttons .button.next.final span { + background: url(../images/icons.png) 0px -349px; + /*+placement:shift 0px 5px;*/ + position: relative; + left: 0px; + top: 5px; + padding: 5px 0px 5px 30px; +} + +.multi-wizard .buttons .button.cancel { + border: none; + /*+placement:float-right -127px 0px;*/ + float: right; + position: relative; + left: -127px; + top: 0px; + color: #808080; + /*+border-radius:0;*/ + -moz-border-radius: 0; + -webkit-border-radius: 0; + -khtml-border-radius: 0; + border-radius: 0; + border-radius: 0 0 0 0; + background: transparent; + /*+box-shadow:0px 0px;*/ + -moz-box-shadow: 0px 0px; + -webkit-box-shadow: 0px 0px; + -o-box-shadow: 0px 0px; + box-shadow: 0px 0px; + -moz-box-shadow: 0px 0px inherit; + -webkit-box-shadow: 0px 0px inherit; + -o-box-shadow: 0px 0px inherit; + -moz-box-shadow: inherit; + -webkit-box-shadow: inherit; + -o-box-shadow: inherit; + /*+text-shadow:0px 1px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px 1px #FFFFFF; + -o-text-shadow: 0px 1px 1px #FFFFFF; + text-shadow: 0px 1px 1px #FFFFFF; + padding: 14px 0 0px 0px; +} + +.multi-wizard .buttons .button.cancel:hover { + color: #5E5E5E; +} + +.multi-wizard .buttons .button.previous { + background: #D6D6D6; + color: #62798E; + margin-left: 27px; +} + +.multi-wizard .buttons .button.previous:hover { + background: #C6C6C6; +} + +/** Instance wizard +** Select ISO*/ +.multi-wizard.instance-wizard .select-iso .select-container { + height: 260px; + margin: 0; + /*+border-radius:0 0 5px 5px;*/ + -moz-border-radius: 0 0 5px 5px; + -webkit-border-radius: 0 0 5px 5px; + -khtml-border-radius: 0 0 5px 5px; + border-radius: 0 0 5px 5px; +} + +/*** Data disk offering*/ +.multi-wizard.instance-wizard .content .section { + padding: 9px 0 16px; + margin: 12px 0 15px 8px; +} + +.multi-wizard.instance-wizard .data-disk-offering .select-container { + height: 300px; + margin: -7px 6px 0 8px; + /*+border-radius:6px;*/ + -moz-border-radius: 6px; + -webkit-border-radius: 6px; + -khtml-border-radius: 6px; + border-radius: 6px; + border-radius: 6px 6px 6px 6px; +} + +.multi-wizard.instance-wizard .data-disk-offering .disk-select-group { + float: left; + margin-top: 12px; + width: 100%; +} + +.multi-wizard.instance-wizard .data-disk-offering .disk-select-header { + border-bottom: 1px solid #D4D4D4; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + background: #C2C2C2 0px 4px; + padding: 6px; + height: 17px; +} + +.multi-wizard.instance-wizard .disk-select-group.selected .disk-select-header { + background: #505050; + /*+border-radius:4px 4px 0 0;*/ + -moz-border-radius: 4px 4px 0 0; + -webkit-border-radius: 4px 4px 0 0; + -khtml-border-radius: 4px 4px 0 0; + border-radius: 4px 4px 0 0; +} + +.multi-wizard.instance-wizard .data-disk-offering .disk-select-header input { + float: left; +} + +.multi-wizard.instance-wizard .data-disk-offering .disk-select-header .title { + float: left; + font-size: 14px; + padding: 2px; +} + +.multi-wizard.instance-wizard .disk-select-group.selected .disk-select-header .title { + color: #FFFFFF; + /*+text-shadow:0px -1px #000000;*/ + -moz-text-shadow: 0px -1px #000000; + -webkit-text-shadow: 0px -1px #000000; + -o-text-shadow: 0px -1px #000000; + text-shadow: 0px -1px #000000; +} + +.multi-wizard.instance-wizard .data-disk-offering .multi-disk-select-container { + max-height: 257px; + overflow: auto; + border: 1px solid #DDDBDB; + padding: 13px; + background: #E4E4E4; +} + +.multi-wizard.instance-wizard .data-disk-offering .disk-select-group .select-container { + max-height: 114px; + float: left; + margin: 0; + border: none; + /*+border-radius:0;*/ + -moz-border-radius: 0; + -webkit-border-radius: 0; + -khtml-border-radius: 0; + border-radius: 0; + display: none; +} + +.multi-wizard.instance-wizard .data-disk-offering .disk-select-group.selected .select-container { + display: block; +} + +.multi-wizard.instance-wizard .data-disk-offering .disk-select-group .select { + padding: 0 0 17px; + height: 0; +} + +.multi-wizard.instance-wizard .data-disk-offering .disk-select-group.custom-size .section.custom-size { + display: block !important; +} + +.multi-wizard.instance-wizard .data-disk-offering .disk-select-group .select input { + margin: 13px 12px 12px; +} + +.multi-wizard.instance-wizard .data-disk-offering .disk-select-group .select-desc { + margin: 13px 0 0; +} + +.multi-wizard.instance-wizard .data-disk-offering.required .select-container { + height: 344px; + position: relative; + margin-top: 9px !important; +} + +.multi-wizard.instance-wizard .custom-disk-size .select-container { + height: 279px; +} + +.multi-wizard.instance-wizard .custom-disk-size .select-container { + height: 213px; + margin: -7px 6px 0 8px; + /*+border-radius:6px;*/ + -moz-border-radius: 6px; + -webkit-border-radius: 6px; + -khtml-border-radius: 6px; + border-radius: 6px; + border-radius: 6px 6px 6px 6px; +} + +.multi-wizard.instance-wizard .content .section input { + float: left; +} + +.multi-wizard.instance-wizard .content .section input[type=radio] { + margin: 8px 2px 0 17px; +} + +.multi-wizard.instance-wizard .content .section label { + display: block; + float: left; + margin: 10px 7px 7px; +} + +.multi-wizard.instance-wizard .content .section label.size { + color: #647A8E; + font-weight: bold; + /*+text-shadow:0px 1px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px 1px #FFFFFF; + -o-text-shadow: 0px 1px 1px #FFFFFF; + text-shadow: 0px 1px 1px #FFFFFF; +} + +.multi-wizard.instance-wizard .section.custom-size { + position: relative; + background: #F4F4F4; + padding: 7px; + border-radius: 4px; +} + +.multi-wizard.instance-wizard .section.custom-iops { + position: relative; + background: #F4F4F4; + padding: 7px; + border-radius: 4px; +} + +.multi-wizard.instance-wizard .section.custom-iops-do { + position: relative; + background: #F4F4F4; + padding: 7px; + border-radius: 4px; +} + +.multi-wizard.instance-wizard .section.custom-size input[type=radio] { + float: left; +} + +.multi-wizard.instance-wizard .section.custom-size input[type=text] { + float: left; + width: 28px; + margin: 6px -1px 0 8px; +} + +.multi-wizard.instance-wizard .section.custom-iops input[type=text] { + float: left; + width: 28px; + margin: 6px -1px 0 8px; +} + +.multi-wizard.instance-wizard .section.custom-iops-do input[type=text] { + float: left; + width: 28px; + margin: 6px -1px 0 8px; +} + +.multi-wizard.instance-wizard .section.custom-size label.error { + position: absolute; + top: 29px; + left: 242px; + font-size: 10px; +} + +.instance-wizard .step.data-disk-offering.custom-disk-size .select-container { + height: 272px; +} + +.instance-wizard .step.data-disk-offering.custom-iops-do .select-container { + height: 240px; +} + +.instance-wizard .step.data-disk-offering.custom-disk-size.custom-iops-do .select-container { + height: 176px; +} + +.instance-wizard .step.data-disk-offering.required.custom-disk-size .select-container { + height: 315px; +} + +.instance-wizard .step.data-disk-offering.required.custom-iops-do .select-container { + height: 295px; +} + +.instance-wizard .step.data-disk-offering.required.custom-disk-size.custom-iops-do .select-container { + height: 223px; +} + +.instance-wizard .step.data-disk-offering .custom-iops-do { + display: none; +} + +.instance-wizard .step.data-disk-offering.custom-iops-do .custom-iops-do { + display: block; +} + +.instance-wizard .step.data-disk-offering .custom-iops-do .field { + width: 30%; + float: left; + margin-bottom: 5px; +} + +.instance-wizard .step.data-disk-offering .custom-iops-do .field label { + text-indent: 20px; +} + +.instance-wizard .step.data-disk-offering .custom-iops-do .field input { + width: 88%; + margin-left: 26px; +} + +/*** Compute offering*/ +.instance-wizard .step.service-offering { +} + +.instance-wizard .step.service-offering.custom-size .select-container { + height: 235px; +} + +.instance-wizard .step.service-offering.custom-iops .select-container { + height: 235px; +} + +.instance-wizard .step.service-offering .custom-size { + display: none; +} + +.instance-wizard .step.service-offering .custom-iops { + display: none; +} + +.instance-wizard .step.service-offering.custom-size .custom-size { + display: block; +} + +.instance-wizard .step.service-offering.custom-iops .custom-iops { + display: block; +} + +.instance-wizard .step.service-offering .custom-size .field { + width: 30%; + float: left; + margin-bottom: 13px; +} + +.instance-wizard .step.service-offering .custom-iops .field { + width: 30%; + float: left; + margin-bottom: 13px; +} + +.instance-wizard .step.service-offering .custom-size .field label { + text-indent: 20px; +} + +.instance-wizard .step.service-offering .custom-iops .field label { + text-indent: 20px; +} + +.instance-wizard .step.service-offering .custom-size .field input { + width: 88%; + margin-left: 26px; +} + +.instance-wizard .step.service-offering .custom-size .field label.error { + position: relative; + top: 0; + left: 0; +} + +.instance-wizard .step.service-offering .custom-iops .field input { + width: 88%; + margin-left: 26px; +} + +/*** Network*/ +.multi-wizard.instance-wizard .no-network { + background: #FFFFFF; + width: 773px; + height: 52px; + position: absolute; + top: 98px; + left: 11px; + /*+border-radius:5px;*/ + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + -khtml-border-radius: 5px; + border-radius: 5px; + border-radius: 5px 5px 5px 5px; + z-index: 1; + padding: 162px 0 194px; +} + +.multi-wizard.instance-wizard .no-network p { + font-size: 22px; + line-height: 25px; +} + +.multi-wizard.instance-wizard .select-network .select table { + width: 405px; + margin: 4px 12px 0; + float: left; +} + +.multi-wizard.instance-wizard .select-network .select table thead { + margin: 0; + padding: 0; +} + +.multi-wizard.instance-wizard .select-network .select table td { + padding: 0; + margin: 0; + vertical-align: top; +} + +.multi-wizard.instance-wizard .select-network .select table .select-container { + margin: 0px; + border: 0; + height: 223px; +} + +.multi-wizard.instance-wizard .select-network.no-add-network .select table .select-container { + height: 282px; +} + +.multi-wizard.instance-wizard .select-network .select.new-network table .select-container { + height: 29px; + overflow: visible; +} + +.multi-wizard.instance-wizard .select-network .select.new-network table .select-container .select { + margin: -12px 0 0; + position: relative; + text-align: right; +} + +.multi-wizard.instance-wizard .select-network .select.new-network table .select-container .select select { + width: 145px; + margin: 4px 0 0; + /*+placement:shift;*/ + position: relative; + left: 0; + top: 0; +} + +.multi-wizard.instance-wizard .select-network .select.new-network { + margin: -17px 0 0; +} + +.multi-wizard.instance-wizard .select-network.no-add-network .select.new-network { + display: none !important; +} + +.multi-wizard.instance-wizard .select-network .main-desc { + width: 252px; + top: 12px; + left: 12px; + float: left; +} + +.multi-wizard.instance-wizard .select-network .select .secondary-input { + float: right; + width: 80px; + height: 48px; + font-size: 11px; + border-left: 1px solid #D7D7D7; + color: #000000; +} + +.multi-wizard.instance-wizard .select-network .select.advanced .secondary-input { + height: 73px; +} + +.multi-wizard.instance-wizard .select-network .select .secondary-input input { + margin: 0 !important; + padding: 0 !important; + /*+placement:shift 9px 21px;*/ + position: relative; + left: 9px; + top: 21px; +} + +.multi-wizard.instance-wizard .select-network .select .secondary-input .name { + float: right; + /*+placement:shift -16px 22px;*/ + position: relative; + left: -16px; + top: 22px; +} + +.multi-wizard.instance-wizard .select-network .select-container .select { + width: 100%; + float: left; + padding: 0; + position: relative; +} + +.multi-wizard.instance-wizard .select-network .select-container .select.advanced { + height: 74px; +} + +.multi-wizard.instance-wizard .select-network .select .advanced-options { + background: url(../images/sprites.png) -7px -795px; + width: 20px; + height: 20px; + float: right; + cursor: pointer; + margin-top: 15px; + margin-right: 13px; +} + +.multi-wizard.instance-wizard .select-network .select .advanced-options:hover, +.multi-wizard.instance-wizard .select-network .select.advanced .advanced-options { + background: url(../images/sprites.png) -32px -795px; +} + +.multi-wizard.instance-wizard .select-network .select .specify-ip { + display: none; + position: absolute; + top: 45px; + left: 0px; + width: 100%; +} + +.multi-wizard.instance-wizard .select-network .select.advanced .specify-ip { + display: block; +} + +.multi-wizard.instance-wizard .select-network .select.advanced .specify-ip input { + margin: 0px 0 0 15px; + width: 138px; +} + +.multi-wizard.instance-wizard .select-network .select-container .select input { + margin: 21px 15px 0px; + float: left; +} + +.multi-wizard.instance-wizard .select-network .select-container .select label { + float: left; + font-size: 11px; + margin: 4px 0 0 42px; + color: #4E6B82; +} + +.multi-wizard.instance-wizard .select-network .select-vpc { + float: left; + padding: 3px; + margin: 7px 0px 7px 7px; +} + +.multi-wizard.instance-wizard .select-network.no-add-network .select-vpc { + visibility: hidden !important; +} + +.multi-wizard.instance-wizard .select-network .select-vpc select { + width: 124px; +} + +.multi-wizard.instance-wizard .select-network .select-vpc label { + font-size: 10px; +} + +/**** New networ*/ +.multi-wizard.instance-wizard .select-network .select.new-network { +} + +.multi-wizard.instance-wizard .select-network .select.new-network .advanced-options { + /*+placement:shift 379px 15px;*/ + position: relative; + left: 379px; + top: 15px; + position: absolute; +} + +.multi-wizard.instance-wizard .select-network .select.new-network .select.advanced { + height: 106px; + position: relative; +} + +.multi-wizard.instance-wizard .select-network .select.new-network.unselected .select.advanced { + height: auto; +} + +.multi-wizard.instance-wizard .select-network .select.new-network .select.advanced .specify-ip { + top: 74px; + left: 29px; +} + +.multi-wizard.instance-wizard .select-network .select.new-network .hide-if-selected { + display: none; +} + +.multi-wizard.instance-wizard .select-network .select.new-network.unselected .hide-if-unselected { + display: none; +} + +.multi-wizard.instance-wizard .select-network .select.new-network.unselected .hide-if-selected { + display: block; +} + +.multi-wizard.instance-wizard .select-network .select.new-network input { + margin-top: 24px; +} + +.multi-wizard.instance-wizard .select-network .select.new-network .field { + /*+placement:shift 41px 21px;*/ + position: relative; + left: 41px; + top: 21px; + font-size: 11px; + color: #000000; + position: absolute; +} + +.multi-wizard.instance-wizard .select-network .select.new-network .field .name { + width: 99px; + float: left; + padding: 3px 0 0; + /*[empty]display:;*/ +} + +.multi-wizard.instance-wizard .select-network .select.new-network .field .value { + float: left; +} + +.multi-wizard.instance-wizard .select-network .select.new-network .field .value input { + width: 138px; + margin: 0 0 0 11px; +} + +.multi-wizard.instance-wizard .select-network .select.new-network label.error { + display: none !important; +} + +.multi-wizard.instance-wizard .select-network .select.new-network .secondary-input { + width: 97px; + padding: 13px 0 17px; +} + +.multi-wizard.instance-wizard .select-network .select.new-network .secondary-input .name { + margin: 0 17px 0 0; +} + +.multi-wizard.instance-wizard .select-network .select.new-network .select-desc { + width: 255px; +} + +.multi-wizard.instance-wizard .select-network .select-container .select .select-desc .desc { + font-size: 11px; + color: #808080; + float: left; + max-width: 113px; +} + +.multi-wizard.instance-wizard .select-network .select-container .select .select-desc .name { + width: 116px; + font-size: 11px; + font-weight: normal; + float: left; + color: #000000; + margin: 0 16px 0 0; +} + +.multi-wizard.instance-wizard .select-network .select.new-network .select-desc .name { + width: 99px; + margin: 4px 0 0; +} + +.multi-wizard.instance-wizard .select-network .select.new-network.unselected .select-desc .name { + color: #808080; +} + +.multi-wizard.instance-wizard .select-network .select.new-network .select-desc .desc { +} + +/*** Confirmation*/ +.multi-wizard .review { +} + +.multi-wizard .review .select-container { +} + +.multi-wizard .review .select-container .select { + height: 35px; + padding: 0; +} + +.multi-wizard .review .select-container .select .name { + float: left; + margin: 13px 22px 0 14px; + width: 127px; + font-size: 11px; +} + +.multi-wizard .review .select-container .select .value { + float: left; + margin: 9px 21px 0; + width: 136px; +} + +.multi-wizard .review .select-container .select .value span { + font-size: 10px; +} + +.multi-wizard .review .select-container .select .edit { + float: right; + cursor: pointer; + height: 18px; + margin: 11px 20px 0 0px; + background: url(../images/icons.png) -10px -452px; + padding: 0px 0px 0 20px; +} + +.multi-wizard .review .select-container .select.odd .edit a { + background: #EBEFF4; +} + +.multi-wizard .review .select-container .select .edit a { + font-size: 10px; + color: #0000FF; + text-decoration: none; + padding: 5px 0 8px; + background: #FFFFFF; +} + +.multi-wizard .review .select-container .select input, +.multi-wizard .review .select-container .select select { + margin: 0; + width: 151px; + float: left; +} + +/*** Diagram*/ +.multi-wizard.instance-wizard .diagram { + width: 1px; + height: 502px; + position: absolute; + top: 109px; + left: 465px; +} + +.multi-wizard.instance-wizard .diagram .part { + background: url(../images/instance-wizard-parts.png) no-repeat 0px 0px; +} + +.multi-wizard.instance-wizard .diagram .part.zone-plane { + width: 354px; + height: 117px; + background-position: 0px -55px; + /*+placement:displace -38px 259px;*/ + position: absolute; + margin-left: -38px; + margin-top: 259px; +} + +.multi-wizard.instance-wizard .diagram .part.computer-tower-front { + width: 95px; + height: 254px; + background-position: -54px -210px; + /*+placement:displace 44px 92px;*/ + position: absolute; + margin-left: 44px; + margin-top: 92px; +} + +.multi-wizard.instance-wizard .diagram .part.computer-tower-back { + width: 194px; + height: 271px; + background-position: -148px -192px; + /*+placement:displace 138px 74px;*/ + position: absolute; + margin-left: 138px; + margin-top: 74px; +} + +.multi-wizard.instance-wizard .diagram .part.os-drive { + width: 194px; + height: 86px; + background-position: -348px -192px; + /*+placement:displace 138px 74px;*/ + position: absolute; + margin-left: 138px; + margin-top: 74px; +} + +.multi-wizard.instance-wizard .diagram .part.cpu { + width: 194px; + height: 49px; + background-position: -344px -278px; + /*+placement:displace 138px 156px;*/ + position: absolute; + margin-left: 138px; + margin-top: 156px; +} + +.multi-wizard.instance-wizard .diagram .part.hd { + width: 194px; + height: 44px; + background-position: -344px -331px; + /*+placement:displace 138px 208px;*/ + position: absolute; + margin-left: 138px; + margin-top: 208px; +} + +.multi-wizard.instance-wizard .diagram .part.network-card { + width: 194px; + height: 44px; + background-position: -344px -380px; + /*+placement:displace 138px 260px;*/ + position: absolute; + margin-left: 138px; + margin-top: 260px; +} + +/** Add zone wizard*/ +.multi-wizard.zone-wizard { + display: block; + height: 675px; +} + +.multi-wizard.zone-wizard ul.subnav { + text-align: left; + /*+placement:shift 30px 104px;*/ + position: relative; + left: 30px; + top: 104px; + position: absolute; + list-style: disc inside; +} + +.multi-wizard.zone-wizard ul.subnav li { + float: left; + padding: 0; + font-size: 12px; + white-space: nowrap; + text-transform: uppercase; + list-style: none; + height: 20px; + margin-right: 34px; + color: #9A9A9A; +} + +.multi-wizard.zone-wizard ul.subnav li:after { + content: ">"; + font-size: 13px; + /*+placement:shift 4px -1px;*/ + position: relative; + left: 4px; + top: -1px; +} + +.multi-wizard.zone-wizard ul.subnav li.active { + color: #0000FF; +} + +.multi-wizard.zone-wizard .ui-tabs-panel { + height: 422px; + overflow: auto; + overflow-x: hidden; +} + +.multi-wizard.zone-wizard ul.ui-tabs-nav { + /*+placement:shift 0px 0px;*/ + position: relative; + left: 0px; + top: 0px; + float: left; + margin: 6px 0 3px 3px; +} + +.multi-wizard.zone-wizard .select-container { + height: 333px; + overflow: auto; +} + +.multi-wizard.zone-wizard .setup-guest-traffic .select-container { + background: #E9EAEB; + overflow: hidden; +} + +.multi-wizard.zone-wizard .setup-guest-traffic.basic .select-container { + background: #FFFFFF; +} + +.multi-wizard.zone-wizard .main-desc { + width: 516px; + float: left; + /*+placement:shift 0;*/ + position: relative; + left: 0; + top: 0; + margin: 23px 0 0px 6px; + font-weight: 100; + font-size: 14px; + color: #424242; +} + +.multi-wizard.zone-wizard .review .main-desc.pre-setup { + width: 90%; + font-size: 18px; + color: #2C4159; + background: url(../images/icons.png) no-repeat 74px -224px; + padding: 1px 0 1px 20px; + text-align: center; + margin-left: 50px; + font-weight: 100; + /*+placement:shift 0px 153px;*/ + position: relative; + left: 0px; + top: 153px; + left: -10px; + /*+text-shadow:0px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px #FFFFFF; + -o-text-shadow: 0px 1px #FFFFFF; + text-shadow: 0px 1px #FFFFFF; +} + +.multi-wizard.zone-wizard .info-desc { + font-size: 11px; + float: left; + text-align: left; + overflow: auto; + overflow-x: hidden; + padding: 11px; + width: 698px; + margin: 29px 0 68px 5px; + background: #FFFFFF; + border: 1px solid #C7C7C7; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; +} + +.multi-wizard.zone-wizard .setup-storage-traffic .info-desc { + margin-bottom: 10px; +} + +.multi-wizard.zone-wizard .setup-public-traffic .info-desc, +.multi-wizard.zone-wizard .setup-guest-traffic .info-desc, +.multi-wizard.zone-wizard .setup-physical-network .info-desc { + margin-bottom: 12px; +} + +.multi-wizard.zone-wizard .info-desc strong { + font-weight: bold; +} + +.multi-wizard.zone-wizard .main-desc em { + text-decoration: underline; + font-weight: bold; +} + +.multi-wizard.zone-wizard .buttons { + top: 609px; +} + +.multi-wizard.zone-wizard .progress ul { + width: 776px; +} + +.multi-wizard.zone-wizard .progress ul li { + width: 107px; + margin-left: 7px; + padding: 0 32px 0 0; +} + +.multi-wizard.zone-wizard .progress ul li span { + width: 102px; + left: 28px; +} + +.multi-wizard.zone-wizard .progress ul li span.number { + left: -28px; +} + +.multi-wizard.zone-wizard .progress ul li span.arrow { + margin: -4px 0 0 109px; +} + +.multi-wizard.zone-wizard .select-network .content .section { + width: 665px; + height: 430px; + /*+placement:shift 0px 14px;*/ + position: relative; + left: 0px; + top: 14px; +} + +.multi-wizard.zone-wizard .select-network .content { + width: 100%; + height: 461px; + float: none; + margin: 7px auto auto; + padding-bottom: 28px; +} + +.multi-wizard.zone-wizard .select-network-model .select-area { + height: 181px; + width: 586px; + position: relative; +} + +.multi-wizard.zone-wizard .select-network-model .select-area.basic-zone { + height: 105px; +} + +.multi-wizard.zone-wizard .select-network-model .select-area.advanced-zone { + height: 233px; +} + +.multi-wizard.zone-wizard .select-network-model .select-area .isolation-mode { + height: 98px; + overflow: hidden; + float: left; + margin: 5px 0 0; + position: absolute; + top: 114px; + left: 9px; +} + +.multi-wizard.zone-wizard .select-network-model .select-area.disabled .isolation-mode { + /*+opacity:50%;*/ + filter: alpha(opacity=50); + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=50); + -moz-opacity: 0.5; + opacity: 0.5; +} + +.multi-wizard.zone-wizard .select-network-model .select-area.disabled .isolation-mode input { +} + +.multi-wizard.zone-wizard .select-network-model .select-area .isolation-mode .title { + font-size: 15px; + color: #5D7387; + /*+placement:shift 36px 2px;*/ + position: relative; + left: 36px; + top: 2px; +} + +.multi-wizard.zone-wizard .select-network-model .select-area .desc { + width: 373px; + height: 70px; + color: #727272; + background: #EFEFEF; + padding: 12px 18px 25px; + /*+placement:shift -27px 12px;*/ + position: relative; + left: -27px; + top: 12px; + /*+border-radius:7px;*/ + -moz-border-radius: 7px; + -webkit-border-radius: 7px; + -khtml-border-radius: 7px; + border-radius: 7px; + border-radius: 7px 7px 7px 7px; + line-height: 19px; +} + +.multi-wizard.zone-wizard .select-network-model .select-area.basic-zone .desc { + padding-bottom: 4px; +} + +.multi-wizard.zone-wizard .select-network-model .select-area .desc em { + font-weight: bold; + text-decoration: underline; +} + +.multi-wizard.zone-wizard .select-network-model .select-area .isolation-mode .select-area { + width: 586px; + height: 61px; + overflow: hidden; + margin: 0; + padding: 0px 0 9px; + background: none; +} + +.multi-wizard.zone-wizard .select-network-model .select-area .isolation-mode .select-area label { + font-size: 11px; + margin: 24px 0 0 2px; +} + +.multi-wizard.zone-wizard .select-network-model .select-area .isolation-mode .select-area input { + margin: 26px 0 11px; +} + +.multi-wizard.zone-wizard .select-network-model .select-area .isolation-mode .select-area input { + margin: 24px 8px 0 12px !important; + padding: 0 !important; +} + +.multi-wizard.zone-wizard .select-network-model .select-area .isolation-mode .select-area .desc { + padding: 6px 7px 11px; + width: 388px; + height: 29px; + font-size: 11px; + margin-right: 9px; + background: #EFEFEF; + float: right; + /*+placement:shift -27px 12px;*/ + position: relative; + left: -27px; + top: 12px; +} + +.multi-wizard.zone-wizard .content.input-area { + width: 721px; + min-height: inherit; + margin: -50px auto auto 4px; + overflow: auto; + overflow-x: hidden; +} + +/*** Add physical network -- network form items*/ +.multi-wizard.zone-wizard .setup-physical-network .content.input-area { + width: 627px; + height: 396px; + background: transparent; + position: relative; +} + +.multi-wizard.zone-wizard .setup-physical-network .drag-helper-icon { + width: 80px; + height: 84px; + background: url(../images/sprites.png) no-repeat 0px -1365px; + /*+placement:shift 134px 303px;*/ + position: relative; + left: 134px; + top: 303px; + position: absolute; +} + +.multi-wizard.zone-wizard .select-container.multi { + display: inline-block; + width: 490px; + margin: 6px auto auto; + float: right; + height: auto; + border: 1px solid #BFBFBF; + overflow: visible; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; + /*+box-shadow:inset 0px 1px 2px #CBCACA;*/ + -moz-box-shadow: inset 0px 1px 2px #CBCACA; + -webkit-box-shadow: inset 0px 1px 2px #CBCACA; + -o-box-shadow: inset 0px 1px 2px #CBCACA; + box-shadow: inset 0px 1px 2px #CBCACA; + background: #F8F6F6; + /*[empty]display:;*/ +} + +.multi-wizard.zone-wizard .select-container.multi.disabled { + border: 1px dotted #A7A7A7; + /*+opacity:40%;*/ + filter: alpha(opacity=40); + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40); + -moz-opacity: 0.4; + opacity: 0.4; +} + +.multi-wizard.zone-wizard .select-container.multi .physical-network-icon { + width: 61px; + height: 53px; + float: left; + border-right: 1px solid #CDCDCD; + background: url(../images/sprites.png) -109px -1393px; +} + +.multi-wizard.zone-wizard .select-container.multi input { + width: 195px !important; + margin: 2px 0 0 17px !important; +} + +.multi-wizard.zone-wizard .select-container.multi .field { + width: 425px; + height: 46px; + margin-top: -6px; +} + +.multi-wizard.zone-wizard .select-container.multi .field .name { + width: 93%; + margin-left: 17px; +} + +.multi-wizard.zone-wizard .select-container.multi .drop-container { + background: #DAE2EC; + width: 484px; + height: 114px; + clear: both; + position: relative; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; + border: 3px dashed #BBBFC4; +} + +.multi-wizard.zone-wizard .select-container.multi .drop-container ul { + background: #DAE2EC; + /*+border-radius:5px;*/ + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + -khtml-border-radius: 5px; + border-radius: 5px; + border-radius: 5px 5px 5px 5px; + width: 99%; + height: 94%; + top: 4px; + left: 2px; + position: absolute; +} + +.multi-wizard.zone-wizard .select-container.multi .drop-container ul.active { + background: #DFEAFF; +} + +.multi-wizard.zone-wizard .select-container.multi .drop-container ul li { + float: left; + margin: 2px 17px 0 29px; +} + +.multi-wizard.zone-wizard .select-container.multi .drop-container span.empty-message { + text-align: center; + color: #959BA0; + font-size: 13px; + /*+placement:shift 0px 45px;*/ + position: relative; + left: 0px; + top: 45px; +} + +/*** Add physical network -- traffic type drag area*/ +.multi-wizard.zone-wizard .traffic-types-drag-area { + width: 96px; + height: 370px; + background: #F0F1F2; + border: 1px solid #DCCACA; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; + float: left; + text-align: left; + padding: 0; + margin: 8px 0 0; + /*+placement:shift 3px 0px;*/ + position: relative; + left: 3px; + top: 0px; +} + +.multi-wizard.zone-wizard .traffic-types-drag-area .header { + margin: 0; + font-size: 13px; + font-weight: bold; + color: #5C5C5C; + /*+text-shadow:0px 1px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px 1px #FFFFFF; + -o-text-shadow: 0px 1px 1px #FFFFFF; + text-shadow: 0px 1px 1px #FFFFFF; + background: #F8F8F8; + text-align: center; + padding: 8px 0 7px; + /*+border-radius:4px 4px 0 0;*/ + -moz-border-radius: 4px 4px 0 0; + -webkit-border-radius: 4px 4px 0 0; + -khtml-border-radius: 4px 4px 0 0; + border-radius: 4px 4px 0 0; + border-bottom: 1px solid #DCCACA; +} + +.multi-wizard.zone-wizard .traffic-types-drag-area > ul { + width: 100%; +} + +.multi-wizard.zone-wizard .traffic-types-drag-area > ul > li { + float: left; + font-size: 11px; + width: 100%; + background: transparent; + height: 83px; + margin: 16px 13px 0 0; +} + +.multi-wizard.zone-wizard .traffic-types-drag-area > ul > li.required { + display: none; +} + +.multi-wizard.zone-wizard .traffic-types-drag-area > ul > li.required.clone { + display: block; +} + +.multi-wizard.zone-wizard .traffic-types-drag-area > ul > li ul.container { + width: 60px; + height: 54px; + margin-left: 16px; + background: #E4E4E4; + /*+border-radius:5px;*/ + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + -khtml-border-radius: 5px; + border-radius: 5px; + border-radius: 5px 5px 5px 5px; + /*+box-shadow:inset 0px 2px 4px #999696;*/ + -moz-box-shadow: inset 0px 2px 4px #999696; + -webkit-box-shadow: inset 0px 2px 4px #999696; + -o-box-shadow: inset 0px 2px 4px #999696; + box-shadow: inset 0px 2px 4px #999696; + border-bottom: 1px solid #FFFFFF; +} + +.multi-wizard.zone-wizard .traffic-types-drag-area > ul > li ul.container li { + /*+placement:shift 1px 2px;*/ + position: relative; + left: 1px; + top: 2px; +} + +.multi-wizard.zone-wizard li.traffic-type-draggable { + display: block; + background: transparent url(../images/sprites.png) no-repeat 0px -1161px; + width: 51px; + height: 51px; + margin: auto; + cursor: move; + z-index: 5000; +} + +.multi-wizard.zone-wizard .traffic-types-drag-area li.traffic-type-draggable:hover, +.multi-wizard.zone-wizard .select-container.multi li.traffic-type-draggable:hover { + width: 69px !important; + height: 66px !important; +} + +.multi-wizard.zone-wizard .traffic-types-drag-area li.traffic-type-draggable:hover, +.multi-wizard.zone-wizard .select-container.multi li.traffic-type-draggable:hover { + /*+placement:shift -2px -4px;*/ + position: relative; + left: -2px; + top: -4px; +} + +.multi-wizard.zone-wizard .select-container.multi li.traffic-type-draggable:hover { + /*+placement:shift -8px -6px;*/ + position: relative; + left: -8px; + top: -6px; + margin-right: 0px; + width: 70px !important; +} + +.multi-wizard.zone-wizard li.traffic-type-draggable.disabled { + /*+opacity:50%;*/ + filter: alpha(opacity=50); + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=50); + -moz-opacity: 0.5; + opacity: 0.5; + cursor: not-allowed; +} + +.multi-wizard.zone-wizard .traffic-types-drag-area ul > li.disabled { + display: none; +} + +.multi-wizard.zone-wizard li.traffic-type-draggable.disabled { + /*+opacity:50%;*/ + filter: alpha(opacity=50); + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=50); + -moz-opacity: 0.5; + opacity: 0.5; + cursor: not-allowed; +} + +.multi-wizard.zone-wizard li.traffic-type-draggable.management { + background-position: 0px -1161px; + height: 52px; +} + +.multi-wizard.zone-wizard .traffic-types-drag-area li.traffic-type-draggable.management:hover, +.multi-wizard.zone-wizard .select-container.multi li.traffic-type-draggable.management:hover { + background-position: -11px -1225px; +} + +.multi-wizard.zone-wizard .select-container.multi li.traffic-type-draggable.management:hover { + margin-right: -1px; +} + +.multi-wizard.zone-wizard li.traffic-type-draggable.public { + width: 53px; + height: 53px; + background-position: -54px -1160px; +} + +.multi-wizard.zone-wizard .traffic-types-drag-area li.traffic-type-draggable.public:hover, +.multi-wizard.zone-wizard .select-container.multi li.traffic-type-draggable.public:hover { + background-position: -87px -1225px; +} + +.multi-wizard.zone-wizard li.traffic-type-draggable.guest { + background-position: -113px -1161px; +} + +.multi-wizard.zone-wizard .traffic-types-drag-area li.traffic-type-draggable.guest:hover, +.multi-wizard.zone-wizard .select-container.multi li.traffic-type-draggable.guest:hover { + background-position: -166px -1227px; +} + +.multi-wizard.zone-wizard li.traffic-type-draggable.storage { + background-position: -170px -1160px; +} + +.multi-wizard.zone-wizard .traffic-types-drag-area li.traffic-type-draggable.storage:hover, +.multi-wizard.zone-wizard .select-container.multi li.traffic-type-draggable.storage:hover { + background-position: -244px -1224px; +} + +.multi-wizard.zone-wizard .traffic-types-drag-area > ul > li .info { + width: 100%; + margin: 5px 0 0 -2px; + line-height: 14px; + float: left; +} + +.multi-wizard.zone-wizard .traffic-types-drag-area > ul > li .info .title { + text-align: center; + font-weight: bold; + color: #787879; +} + +.multi-wizard.zone-wizard .traffic-types-drag-area > ul > li .info .desc { + display: none; +} + +/*** Traffic type icon -- edit button*/ +.multi-wizard.zone-wizard .traffic-type-draggable .edit-traffic-type { + display: none; +} + +.multi-wizard.zone-wizard .drop-container .traffic-type-draggable > .edit-traffic-type { + cursor: pointer; + display: block; + width: 59px; + height: 23px; + padding: 2px 9px 0 12px; + background: url(../images/bg-gradients.png) 0px -1342px; + border: 1px solid #C4C4C4; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; + border-top: 1px solid #FFFFFF; + /*+placement:shift -16px 70px;*/ + position: relative; + left: -16px; + top: 70px; +} + +.multi-wizard.zone-wizard .drop-container .traffic-type-draggable > .edit-traffic-type:hover { + background-position: 0px -105px; + color: #FFFFFF; + /*+box-shadow:inset 0px -1px 1px #727272;*/ + -moz-box-shadow: inset 0px -1px 1px #727272; + -webkit-box-shadow: inset 0px -1px 1px #727272; + -o-box-shadow: inset 0px -1px 1px #727272; + box-shadow: inset 0px -1px 1px #727272; +} + +.multi-wizard.zone-wizard .drop-container .traffic-type-draggable > .edit-traffic-type:hover span { + color: #FFFFFF; + /*+text-shadow:0px 1px 1px #000000;*/ + -moz-text-shadow: 0px 1px 1px #000000; + -webkit-text-shadow: 0px 1px 1px #000000; + -o-text-shadow: 0px 1px 1px #000000; + text-shadow: 0px 1px 1px #000000; +} + +.multi-wizard.zone-wizard .drop-container .traffic-type-draggable:hover > .edit-traffic-type { + /*+placement:shift -7px 76px;*/ + position: relative; + left: -7px; + top: 76px; +} + +.multi-wizard.zone-wizard .drop-container .traffic-type-draggable .edit-traffic-type span { + text-align: center; + font-size: 11px; + font-weight: bold; + color: #4E73A6; + /*+text-shadow:0px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px #FFFFFF; + -o-text-shadow: 0px 1px #FFFFFF; + text-shadow: 0px 1px #FFFFFF; +} + +.multi-wizard.zone-wizard .drop-container .traffic-type-draggable .edit-traffic-type span.icon { + float: left; + background: url(../images/sprites.png) -7px -4px; + padding: 7px 11px 0 7px; +} + +.multi-wizard.zone-wizard .traffic-type-draggable .edit-traffic-type span.name { + float: left; + width: 76px; + font-size: 10px; + padding: 2px; + color: #4E5F6F; + background: #DBE1E9; + /*+border-radius:4px 4px 0 0;*/ + -moz-border-radius: 4px 4px 0 0; + -webkit-border-radius: 4px 4px 0 0; + -khtml-border-radius: 4px 4px 0 0; + border-radius: 4px 4px 0 0; + /*+placement:shift -13px -16px;*/ + position: relative; + left: -13px; + top: -16px; + margin-bottom: -13px; + border: 1px solid #C3BCBC; + border-bottom: 1px solid #D1CDCD; + /*+box-shadow:inset 0px 1px 1px #F5F4F4;*/ + -moz-box-shadow: inset 0px 1px 1px #F5F4F4; + -webkit-box-shadow: inset 0px 1px 1px #F5F4F4; + -o-box-shadow: inset 0px 1px 1px #F5F4F4; + box-shadow: inset 0px 1px 1px #F5F4F4; +} + +.multi-wizard.zone-wizard .traffic-type-draggable .edit-traffic-type:hover span.name { + background: #C4C3C3; +} + +/*** Configure guest network -- tabs*/ +.multi-wizard.zone-wizard .setup-guest-traffic .ui-widget-content { + width: 682px; + height: 281px; + border-bottom: none; + border-right: none; + /*+placement:shift -1px -7px;*/ + position: relative; + left: -1px; + top: -7px; +} + +.multi-wizard.zone-wizard .setup-guest-traffic ul.ui-tabs-nav { + width: 456px; + /*+placement:shift -4px -8px;*/ + position: relative; + left: -4px; + top: -8px; +} + +.multi-wizard.zone-wizard .setup-guest-traffic .main-desc { + margin-top: 27px; + margin-left: -3px; +} + +.multi-wizard.zone-wizard .setup-guest-traffic .content { + margin-left: -4px; + margin-top: 2px; +} + +/*** Multi-edit*/ +.multi-wizard.zone-wizard .multi-edit { + width: 732px; + float: left; + margin-left: 0px; +} + +.multi-wizard.zone-wizard .multi-edit table { + float: left; + width: 98%; +} + +.multi-wizard.zone-wizard .multi-edit table td, +.multi-wizard.zone-wizard .multi-edit table th { + padding: 4px; +} + +.multi-wizard.zone-wizard .multi-edit table th { + padding-top: 11px; + padding-bottom: 8px; +} + +.multi-wizard.zone-wizard .multi-edit table input { + margin: 2px 0px 2px -5px; + padding: 2px 0px; +} + +.multi-wizard.zone-wizard .multi-edit .data { + float: left; + overflow: visible; +} + +.multi-wizard.zone-wizard .multi-edit .data-body { + margin: 0; +} + +.multi-wizard.zone-wizard .multi-edit .data-body .data-item { + float: left; +} + +.multi-wizard.zone-wizard .multi-edit .data-body .data-item td { + padding-top: 8px; + padding-bottom: 8px; +} + +.multi-wizard.zone-wizard .multi-edit .data-body .data-item td span { + font-size: 10px; + text-overflow: ellipsis; + max-width: 91px; +} + +/*** Select container fields*/ +.multi-wizard.zone-wizard .select-container .field { + width: 100%; + padding-bottom: 13px; + float: left; +} + +.multi-wizard.zone-wizard .select-container .field.odd { + background: #EBEFF5; +} + +.multi-wizard.zone-wizard .select-container .field .name { + float: left; + margin: 18px 0 0 12px; + width: 95px; + font-size: 11px; + text-align: left; + line-height: 13px; +} + +.multi-wizard.zone-wizard .select-container .field .value { + float: left; + position: relative; +} + +.multi-wizard.zone-wizard .select-container .field .value span { + display: block; + font-size: 11px; + color: #052060; + margin: 20px 0 0; +} + +.multi-wizard.zone-wizard .select-container .field .value label.error { + color: #FF0000; + font-size: 10px; + position: absolute; + display: block; + text-align: left; + margin: 2px 0 0 16px; + float: right; + /*+placement:shift 1px 31px;*/ + position: relative; + left: 1px; + top: 31px; + position: absolute; +} + +.multi-wizard.zone-wizard .select-container .field .value input, +.multi-wizard.zone-wizard .select-container .field .value select { + width: 316px; + height: 20px; + margin: 13px 13px 0 18px; + float: left; +} + +.multi-wizard.zone-wizard .select-container .field .range-edit .range-item { + width: 106px; + float: left; +} + +.multi-wizard.zone-wizard .select-container .field .range-edit .range-item input[type=text] { + width: 93px; + margin: 16px 0 0 17px; +} + +.multi-wizard.zone-wizard .select-container .field .value select { + width: 327px; + height: 21px; +} + +.multi-wizard.zone-wizard .select-container .field .value input[type=checkbox] { + float: left; + display: block; + width: 13px; +} + +.multi-wizard.zone-wizard .select-container .field .value.multi-range input { + float: left; + width: 137px; +} + +.multi-wizard.zone-wizard .select-container .field .value.multi-range span { + float: left; + margin: 13px 0 0; +} + +.multi-wizard.zone-wizard .select-container .field .select-array { + width: 360px; + display: inline-block; +} + +/*[clearfix]*/.multi-wizard.zone-wizard .select-container .field .select-array-item { + width: 175px; + height: 34px; + float: left; +} + +.multi-wizard.zone-wizard .select-container .field .select-array-item .name { + width: 127px; + float: right; + padding: 0; + margin: 11px 0 0; +} + +.multi-wizard.zone-wizard .select-container .field .select-array-item .value { + width: 41px; + float: right; + padding: 0; + margin: 0; +} + +.multi-wizard.zone-wizard .select-container .field .select-array-item .value input { + width: inherit; + margin: 12px 0 0 11px; +} + +.multi-wizard.zone-wizard .setup-physical-network .button.add.new-physical-network { + background: #808080 url(../images/bg-gradients.png) 0px -264px; + visibility: hidden; + border: 1px solid #ADA7A7; + padding: 6px 20px 6px 11px; + font-size: 12px; + float: right; + margin: 14px 6px 0 0; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; + color: #475765; + /*+text-shadow:0px 1px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px 1px #FFFFFF; + -o-text-shadow: 0px 1px 1px #FFFFFF; + text-shadow: 0px 1px 1px #FFFFFF; + cursor: pointer; +} + +.multi-wizard.zone-wizard .setup-physical-network .button.remove.physical-network { + padding: 10px 10px 0px; + background: url(../images/sprites.png) -6px -93px; + cursor: pointer; + position: relative; + float: right; + top: 27px; + margin: -26px 0 0; +} + +.multi-wizard.zone-wizard .setup-physical-network .select-container.disabled .button.remove.physical-network { + display: none; +} + +.multi-wizard.zone-wizard .setup-physical-network .button.remove.physical-network:hover { + background-position: -6px -675px; +} + +.multi-wizard.zone-wizard .setup-physical-network .button.add.new-physical-network:hover { + background-position: 0px -349px; + color: #000000; + /*+text-shadow:0px 1px 2px #FFFFFF;*/ + -moz-text-shadow: 0px 1px 2px #FFFFFF; + -webkit-text-shadow: 0px 1px 2px #FFFFFF; + -o-text-shadow: 0px 1px 2px #FFFFFF; + text-shadow: 0px 1px 2px #FFFFFF; +} + +.multi-wizard.zone-wizard .setup-physical-network .button.add.new-physical-network .icon { + padding: 10px; + background: url(../images/sprites.png) -44px -58px; +} + +/*** Review / launch*/ +.multi-wizard.zone-wizard .review .launch-container { + width: 98%; + max-height: 438px; + overflow: auto; + overflow-x: hidden; + float: left; + background: #ECECEC 0px -12px; + background: #F7F7F7; + background: -moz-linear-gradient(top, #f7f7f7 0%, #eaeaea 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f7f7f7), color-stop(100%,#eaeaea)); + background: -webkit-linear-gradient(top, #f7f7f7 0%,#eaeaea 100%); + background: -o-linear-gradient(top, #f7f7f7 0%,#eaeaea 100%); + background: -ms-linear-gradient(top, #f7f7f7 0%,#eaeaea 100%); + background: linear-gradient(to bottom, #f7f7f7 0%,#eaeaea 100%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f7f7f7', endColorstr='#eaeaea',GradientType=0 ); + margin: 11px 0 0 7px; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border: 1px solid #CACACA; + border-radius: 4px 4px 4px 4px; +} + +.multi-wizard.zone-wizard .review .launch-container li { + width: 100%; + text-align: left; + padding: 15px 0 15px 12px; + font-size: 12px; + font-weight: bold; +} + +.multi-wizard.zone-wizard .review .launch-container li .icon { + /*[empty]display:;*/ + padding: 10px 21px 10px 10px; + background: url(../images/icons.png) -2px -217px; +} + +.multi-wizard.zone-wizard .review .launch-container li.loading .icon { + background: url(../images/ajax-loader-small.gif) no-repeat 2px 9px; +} + +.multi-wizard.zone-wizard .review .launch-container li.error .icon { + background-position: -2px -185px; +} + +.multi-wizard.zone-wizard .review .launch-container li.info .icon { + display: none; +} + +/*Tree view*/ +.tree-view { + width: 24%; + height: 98%; + overflow: auto; +} + +.tree-view.overflowScroll { + overflow: scroll; +} + +#browser .tree-view div.toolbar { +} + +.tree-view ul { + display: block; + width: 85px; +} + +.tree-view ul li { + position: relative; + left: 21px; + font-size: 12px; + display: block; + margin: 7px 0 0; + clear: both; + width: 100%; +} + +.tree-view > ul { + /*+placement:shift 3px 40px;*/ + position: relative; + left: 3px; + top: 40px; +} + +.tree-view > ul > li { + left: 5px; +} + +.tree-view ul li .name { + float: left; + margin: 1px 0px 13px 17px; + padding: 6px 9px 6px 4px; + cursor: pointer; +} + +.tree-view ul li .name:hover { + text-decoration: underline; +} + +.tree-view ul li .name.selected { + background: #DDDCDD; + /*+border-radius:5px;*/ + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + -khtml-border-radius: 5px; + border-radius: 5px; + border-radius: 5px 5px 5px 5px; +} + +.tree-view ul li .expand { + width: 10px; + height: 10px; + float: left; + background: url(../images/buttons.png) -630px -245px; + margin: 4px 5px 0 0; + float: left; + cursor: pointer; + position: absolute; +} + +.tree-view ul li.expanded > .expand { + background-position: -631px -228px; +} + +#browser .tree-view div.toolbar div.text-search { + float: left; +} + +#navigation ul li.last.active { +} + +.detail-group table tbody { + border: none; +} + +div.detail-group td.view-all div.view-all div.end { +} + +/*Dialog-based list view*/ +.ui-dialog .list-view { + height: 515px !important; + overflow: auto; + overflow-x: hidden; +} + +.ui-dialog .list-view .toolbar { + top: 50px; + width: 854px; +} + +div.panel.ui-dialog div.list-view div.fixed-header { + top: 55px; + left: 35px; + width: 759px; + height: 49px; + background-color: #FFFFFF; + margin: 0; + z-index: 1; +} + +.ui-dialog .list-view table { + top: 9px !important; +} + +.ui-dialog.panel div.list-view div.data-table table { + width: 778px; + margin-top: 39px; +} + +.ui-dialog.panel div.list-view div.data-table table tbody tr.multi-edit-selected { + background: #C3E0FC; +} + +/*List-view: subselect dropdown*/ +.list-view .subselect { + width: 173px; + cursor: default; + display: block; + float: left; + background: #E8E8E8; + padding: 0; + margin: 0 0 0 -3px; + clear: both; + border: 1px solid #A8A7A7; + /*+border-radius:2px;*/ + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + -khtml-border-radius: 2px; + border-radius: 2px; +} + +.list-view .subselect:hover span { + color: initial; +} + +.list-view .subselect span { + margin: 4px 0 0 12px; + cursor: default; +} + +.list-view .subselect span.info { + background: none; +} + +.list-view .subselect span:hover { + color: initial; +} + +.list-view .subselect select { + width: 175px; + margin: 0 0 0 -11px; + font-size: 10px; +} + +/*Multi-edit*/ +div.container div.panel div#details-tab-addloadBalancer.detail-group div.loadBalancer div.multi-edit form table.multi-edit thead tr th, +div.container div.panel div#details-tab-addloadBalancer.detail-group div.loadBalancer div.multi-edit form table.multi-edit tbody tr td { + min-width: 100px; +} + +.multi-edit { + overflow: auto; +} + +.multi-edit > form { + position: relative; + clear: both; +} + +.multi-edit table.multi-edit { + border-top: none; +} + +.multi-edit table th { + min-width: 88px; + white-space: nowrap; + text-align: center; + text-indent: 0; +} + +.detail-group .multi-edit table td { + border-left: 1px solid #CDCCCC; +} + +.detail-view .multi-edit input { + width: 70%; +} + +.detail-view .multi-edit select { + width: 93%; + font-size: 10px; + min-width: 80px; +} + +.multi-edit input { + width: 85%; +} + +.multi-edit .range { + position: relative; +} + +.multi-edit .range .range-item { + float: left; +} + +.multi-edit .range input { + width: 35px; + margin-right: 2px; + position: relative; +} + +.multi-edit .range label { + display: block; + clear: both; + /*+placement:shift 3px 2px;*/ + position: relative; + left: 3px; + top: 2px; +} + +.multi-edit label.error { + font-size: 10px; + margin: 3px 0 0; + float: left; +} + +.multi-edit .data-table td span { + float: left; +} + +.multi-edit .data-table td.add-vm { + cursor: pointer; +} + +.multi-edit th.add-rule, +.multi-edit td.add-rule { +} + +.multi-edit .data-table td.add-vm:hover { + color: #5FAAF7; +} + +.multi-edit .data-table .fixed-header { + display: none; +} + +.multi-edit .button.add-vm { + font-size: 10px; + font-weight: bold; + cursor: pointer; + color: #FFFFFF; + /*+text-shadow:0px 1px 1px #000000;*/ + -moz-text-shadow: 0px 1px 1px #000000; + -webkit-text-shadow: 0px 1px 1px #000000; + -o-text-shadow: 0px 1px 1px #000000; + text-shadow: 0px 1px 1px #000000; + /*+box-shadow:0px 1px 1px #FFFFFF;*/ + -moz-box-shadow: 0px 1px 1px #FFFFFF; + -webkit-box-shadow: 0px 1px 1px #FFFFFF; + -o-box-shadow: 0px 1px 1px #FFFFFF; + box-shadow: 0px 1px 1px #FFFFFF; + border: 1px solid #858585; + border-top: none; + /*+border-radius:5px;*/ + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + -khtml-border-radius: 5px; + border-radius: 5px; + border-radius: 5px 5px 5px 5px; + width: 74px; + text-indent: 0px; + text-align: center; + padding: 6px 0px 4px; + background: url(../images/bg-gradients.png) repeat-x 0px -220px; + /*+placement:shift 4px 0px;*/ + position: relative; + left: 4px; + top: 0px; +} + +.multi-edit .button.add-vm:hover { + background-position: 0px -241px; + /*+box-shadow:inset 0px 1px 1px #000000;*/ + -moz-box-shadow: inset 0px 1px 1px #000000; + -webkit-box-shadow: inset 0px 1px 1px #000000; + -o-box-shadow: inset 0px 1px 1px #000000; + box-shadow: inset 0px 1px 1px #000000; +} + +.multi-edit .button.custom-action { + background: url(../images/bg-gradients.png) 0px -271px; + border: 1px solid #B7B7B7; + color: #485867; + font-size: 10px; + /*+text-shadow:0px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px #FFFFFF; + -o-text-shadow: 0px 1px #FFFFFF; + text-shadow: 0px 1px #FFFFFF; +} + +.multi-edit td.disabled .button.add-vm.custom-action { + /*+opacity:50%;*/ + filter: alpha(opacity=50); + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=50); + -moz-opacity: 0.5; + opacity: 0.5; + cursor: not-allowed; +} + +.multi-edit .button.custom-action:hover { + background: #808080 url(../images/bg-gradients.png); + color: #FFFFFF; + /*+text-shadow:0px 1px 1px #000000;*/ + -moz-text-shadow: 0px 1px 1px #000000; + -webkit-text-shadow: 0px 1px 1px #000000; + -o-text-shadow: 0px 1px 1px #000000; + text-shadow: 0px 1px 1px #000000; +} + +.multi-edit-add-list .ui-button.ok, +.multi-edit-add-list .ui-button.cancel { + float: right; + /*+placement:shift 506px -18px;*/ + position: relative; + left: 506px; + top: -18px; + height: 23px; +} + +.multi-edit-add-list .ui-button.cancel { + /*+placement:shift 492px -18px;*/ + position: relative; + left: 492px; + top: -18px; + border: none; + background: transparent; + color: #808B95; + font-weight: bold; +} + +.multi-edit-add-list div.form-container { + width: auto !important; + height: auto; + text-align: center; +} + +.multi-edit-add-list div.form-container div.name label { + display: inline; +} + +.multi-edit .data .data-body { + margin: auto auto auto 11px; + overflow: hidden; +} + +.panel.always-maximized .multi-edit .data .data-body { + width: 96%; + margin: 0 0 0 12px; +} + +.multi-edit .data .data-body .data-item { + margin-bottom: 14px; + border: 1px solid #CDCCCC; + position: relative; +} + +.multi-edit .data .data-body .data-item .loading-overlay { + background-position: 50% 50%; +} + +.multi-edit .data .data-body .data-item.loading { + height: 28px; + background: #FFFFFF url(../images/ajax-loader.gif) no-repeat center; + border: 1px solid #DDDDDD; +} + +.multi-edit .data .data-body .data-item.loading .label { + color: #808080; + font-size: 12px; + text-align: center; + text-indent: 19%; + margin: 12px 0 0; +} + +.multi-edit .data .data-body .data-item table { + margin: 0; + border: none; + width: 100%; + overflow: hidden; + background: #F0F1F2; +} + +.multi-edit .data .data-body .data-item tr { + background: #EFEFEF; + border: none; +} + +.multi-edit .data .data-body .data-item table tbody tr td { + background: #F0F1F2; + border-left: none; + border-right: 1px solid #CFC9C9; + height: 15px; + overflow: auto; + padding-right: 0; +} + +.multi-edit .data .data-body .data-item > table tbody tr td span { + overflow: hidden; + max-width: 90%; + display: block; + float: left; + text-overflow: ellipsis; + white-space: nowrap; +} + +.multi-edit .data .data-body .data-item table tbody tr td.blank { +} + +.multi-edit .data .data-body .data-item table tbody tr td.name { + padding-top: 9px; +} + +.multi-edit .data .data-body .data-item table tbody tr td.name span { + width: 53px; + color: #4C5D78; + font-weight: bold; +} + +.multi-edit .data .data-body .data-item .expandable-listing table tbody tr td.name span { + color: #4C5D78; + font-weight: normal; + cursor: pointer; +} + +.multi-edit .data .data-body .data-item .expandable-listing table tbody tr td.name span:hover { + color: #0000FF; +} + +.multi-edit .data .data-body .data-item table tbody tr td.multi-actions { + border-right: none; +} + +.multi-edit .data .data-body .data-item table tbody tr td.multi-actions .action { + cursor: pointer; + width: 28px; + height: 21px; + float: left; +} + +.multi-edit .data .data-body .data-item table tbody tr td.multi-actions .action span.icon { + background-image: url(../images/sprites.png); + cursor: pointer; + width: 28px; + height: 21px; + float: left; +} + +.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; + /*+border-radius:9px;*/ + -moz-border-radius: 9px; + -webkit-border-radius: 9px; + -khtml-border-radius: 9px; + border-radius: 9px; + border-radius: 9px 9px 9px 9px; + float: left; + margin: -3px 0 0 11px; +} + +.multi-edit .data .data-body .data-item tr td.add-vm, +.multi-edit tr th.add-vm { + cursor: pointer; +} + +.multi-edit .data .data-body .data-item tr td .custom-action { + margin: -2px 0 0 0px; +} + +.multi-edit .data .data-body .data-item tr td.add-vm:hover { + color: #0060FF; + font-weight: bold; +} + +.multi-edit .data .data-body .data-item tr td.add-vm p { + text-indent: 0; + padding-left: 9px; + margin-top: 3px; + margin-bottom: 6px; +} + +.multi-edit .data .data-body .data-item tr td.multi-actions .icon { + /*+placement:shift -3px -2px;*/ + position: relative; + left: -3px; + top: -2px; +} + +.multi-edit .data .data-body .data-item .expandable-listing { + width: 99.8%; + border: 1px solid #CFC9C9; + max-height: 161px; + overflow: auto; + overflow-x: hidden; +} + +.multi-edit .data .data-body .data-item .expandable-listing tr { + width: 100%; + border: none; + margin: 0; + padding: 0; +} + +.multi-edit .data .data-body .data-item .expandable-listing tr td { + background: #DDE0E2; + border: none; + margin: 0; + text-indent: 37px; +} + +.multi-edit .data .data-body .data-item .expandable-listing tr.odd td { + background: #F2F0F0; +} + +.ui-tabs-panel .add-by { + font-size: 12px; + color: #536474; + width: 94%; + margin: 13px 0 0 14px; +} + +.ui-tabs-panel .add-by .selection { + width: 236px; + margin: 8px 0 0; +} + +.ui-tabs-panel .add-by .selection input { + margin: 0 6px 0 0; +} + +.ui-tabs-panel .add-by .selection label { + margin: 0 22px 0 0; +} + +/** Fix long table overflow*/ +.detail-view .multi-edit { + width: 100%; +} + +.detail-view .multi-edit table { + width: 97%; + max-width: inherit; +} + +.detail-view .multi-edit table tr th, +.detail-view .multi-edit table tr td { + width: 87px !important; + min-width: 87px !important; + max-width: 87px !important; + font-size: 10px; +} + +/* special case for 'Source CIDR' column - make it wide enough to fit a CIDR without ellipsizing*/ +.detail-view .multi-edit table tr th.cidrlist, +.detail-view .multi-edit table tr td.cidrlist { + min-width: 112px !important; + max-width: 112px !important; +} +.detail-view .multi-edit td.cidrlist input { + width: 85%; +} + + +/** Header fields*/ +.multi-edit .header-fields { + position: relative; + /*+placement:shift 14px 11px;*/ + position: relative; + left: 14px; + top: 11px; +} + +.multi-edit .header-fields .form-container { + width: 96%; + height: 32px; + background: #E4E4E4; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border: 1px solid #D4CFCF; +} + +.multi-edit .header-fields .form-item { + padding: 4px 15px 3px 9px; + margin-bottom: 32px; + margin-right: 0; + float: left; +} + +.multi-edit .header-fields .form-item input, +.multi-edit .header-fields .form-item select { + margin-top: 4px; +} + +.multi-edit .header-fields .form-item .name, +.multi-edit .header-fields .form-item .value { + float: left; +} + +.multi-edit .header-fields .form-item .name { + font-size: 14px; + padding: 5px; + color: #55687A; +} + +.multi-edit .header-fields input[type=submit] { +} + +/*Sortable*/ +.multi-edit table tbody tr td.reorder, +.multi-edit table thead tr th.reorder { + width: 30px !important; + min-width: 30px !important; + max-width: 30px !important; +} + +/*Security Rules*/ +.security-rules .multi-edit input { + width: 69px; + margin: 0 0 0 9px; +} + +.security-rules .multi-edit .range input { + width: 44px; + margin: 0; +} + +/*Recurring snapshots*/ +.recurring-snapshots { + display: inline-block; +} + +.recurring-snapshots .schedule { +} + +.recurring-snapshots .schedule .add-snapshot-actions { + width: 581px; + clear: both; + float: left; + font-size: 13px; + margin-bottom: 13px; + border-top: 1px solid #FFFFFF; +} + +.recurring-snapshots .schedule .add-snapshot-action { + /*+placement:shift -7px -34px;*/ + position: relative; + left: -7px; + top: -34px; + float: right; + cursor: pointer; + padding: 10px; +} + +.recurring-snapshots .schedule .add-snapshot-action.add { + color: #0000FF; + /*+text-shadow:0px 1px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px 1px #FFFFFF; + -o-text-shadow: 0px 1px 1px #FFFFFF; + text-shadow: 0px 1px 1px #FFFFFF; + font-weight: bold; +} + +.recurring-snapshots .schedule .add-snapshot-action.add:hover { + color: #1A85F4; +} + +.recurring-snapshots .schedule p { + float: left; + font-size: 13px; + padding: 0; + margin: 5px 0 0 14px; +} + +.recurring-snapshots .schedule .forms { + width: 436px; + float: left; + /*+placement:shift 24px 2px;*/ + position: relative; + left: 24px; + top: 2px; +} + +.recurring-snapshots .schedule .forms > div { +} + +.recurring-snapshots .schedule .forms form { + font-size: 12px; + color: #4F6171; +} + +.recurring-snapshots .schedule .forms form select { + float: left; + margin: 3px 10px 3px 3px; + max-width: 100%; +} + +.recurring-snapshots .schedule .forms form input { + /*+placement:shift 1px 4px;*/ + position: relative; + left: 1px; + top: 4px; +} + +.recurring-snapshots .schedule .forms form label { + /*+placement:shift 5px 4px;*/ + position: relative; + left: 5px; + top: 4px; +} + +.recurring-snapshots .schedule .forms form label.error { + width: 100%; + float: left; + font-size: 10px; +} + +.recurring-snapshots .schedule .forms form .field { + width: 100%; + float: left; + margin: 8px 0 0; +} + +.recurring-snapshots .schedule .forms form .name { + float: left; + text-align: right; + width: 72px; + padding: 4px 0 0; + margin: 3px 14px 0 0; +} + +.recurring-snapshots .schedule .forms form .value { + width: 470px; + float: left; + text-align: left; +} + +.ui-dialog .recurring-snapshots .ui-widget-content { + padding: 0; + margin: 0; +} + +.recurring-snapshots .ui-button { + /*+placement:anchor-bottom-right 9px 9px;*/ + position: absolute; + right: 9px; + bottom: 9px; +} + +.recurring-snapshots .scheduled-snapshots { + clear: both; + display: inline-block; + /*+placement:shift 0px -26px;*/ + position: relative; + left: 0px; + top: -26px; +} + +.recurring-snapshots .scheduled-snapshots p { + font-weight: bold; + font-size: 12px; + /*+text-shadow:0px 2px 2px #FFFFFF;*/ + -moz-text-shadow: 0px 2px 2px #FFFFFF; + -webkit-text-shadow: 0px 2px 2px #FFFFFF; + -o-text-shadow: 0px 2px 2px #FFFFFF; + text-shadow: 0px 2px 2px #FFFFFF; + text-indent: 12px; + padding-bottom: 8px; +} + +.recurring-snapshots .scheduled-snapshots table { + border: none; + /*+placement:shift 0px -14px;*/ + position: relative; + left: 0px; + top: -14px; + width: 564px; +} + +.recurring-snapshots .scheduled-snapshots table td.actions div.action span.icon { + /*+placement:shift -3px -4px;*/ + position: relative; + left: -3px; + top: -4px; +} + +.recurring-snapshots .scheduled-snapshots tr { + padding: 0; + border: 0; + display: block; + width: 100%; + height: 38px; + margin: 22px 0 0; + display: none; +} + +.recurring-snapshots .scheduled-snapshots tr td { + border: none; + min-width: 102px; + padding: 5px 0px 0 14px; + font-size: 12px; + word-break: keep-all; + word-wrap: normal; + text-indent: 0px; +} + +.recurring-snapshots .scheduled-snapshots tr td.keep { + min-width: 60px; +} + +.recurring-snapshots .scheduled-snapshots tr td.timezone { + min-width: 168px; + font-size: 12px; +} + +.recurring-snapshots .scheduled-snapshots tr td.timezone span { + font-size: 10px; +} + +.recurring-snapshots .scheduled-snapshots table tbody tr td.actions { + max-width: 22px !important; + min-width: 22px; +} + +.recurring-snapshots .scheduled-snapshots tr td.time { + min-width: 144px; + background: url(../images/sprites.png) no-repeat -536px -533px; + text-indent: 0.7em; +} + +.recurring-snapshots .scheduled-snapshots tr.daily td.time { + background-position: -537px -569px; +} + +.recurring-snapshots .scheduled-snapshots tr.weekly td.time { + background-position: -537px -605px; +} + +.recurring-snapshots .scheduled-snapshots tr.monthly td.time { + background-position: -537px -648px; +} + +.recurring-snapshots p { + text-align: left; + font-size: 14px; + line-height: 18px; + padding: 0 47px 0 0; + color: #475765; + margin-bottom: 16px; + /*+text-shadow:0px 3px 3px #FFFFFF;*/ + -moz-text-shadow: 0px 3px 3px #FFFFFF; + -webkit-text-shadow: 0px 3px 3px #FFFFFF; + -o-text-shadow: 0px 3px 3px #FFFFFF; + text-shadow: 0px 3px 3px #FFFFFF; + max-width: 550px; + display: block; +} + +.recurring-snapshots .ui-tabs ul { + position: relative; + margin: 0; + width: 100%; + padding: 0; + margin: 0; + display: block; +} + +.recurring-snapshots .ui-tabs ul li a { + width: 76px; + text-indent: 28px; + margin: 0 14px 0 0; + padding: 8px 0 5px; + background: url(../images/sprites.png) no-repeat -521px -540px; + border: none; +} + +.recurring-snapshots .ui-tabs ul li.disabled a { + /*+opacity:50%;*/ + filter: alpha(opacity=50); + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=50); + -moz-opacity: 0.5; + opacity: 0.5; +} + +.recurring-snapshots .ui-tabs ul li.disabled:hover a { + border: none; + background-color: transparent; + border: none; + /*+box-shadow:0px 0px;*/ + -moz-box-shadow: 0px 0px; + -webkit-box-shadow: 0px 0px; + -o-box-shadow: 0px 0px; + box-shadow: 0px 0px; + -moz-box-shadow: 0px 0px none; + -webkit-box-shadow: 0px 0px none; + -o-box-shadow: 0px 0px none; + -moz-box-shadow: none; + -webkit-box-shadow: none; + -o-box-shadow: none; + cursor: default; +} + +.recurring-snapshots .ui-tabs ul li.ui-state-active a, +.recurring-snapshots .ui-tabs ul li:hover a { + background-color: #DFDFDF; + /*+border-radius:8px;*/ + -moz-border-radius: 8px; + -webkit-border-radius: 8px; + -khtml-border-radius: 8px; + border-radius: 8px; + border-radius: 8px 8px 8px 8px; + /*+box-shadow:inset 0px 1px 2px #4E4E4E;*/ + -moz-box-shadow: inset 0px 1px 2px #4E4E4E; + -webkit-box-shadow: inset 0px 1px 2px #4E4E4E; + -o-box-shadow: inset 0px 1px 2px #4E4E4E; + box-shadow: inset 0px 1px 2px #4E4E4E; +} + +.recurring-snapshots .ui-tabs ul li.daily a { + background-position: -522px -577px; +} + +.recurring-snapshots .ui-tabs ul li.weekly a { + background-position: -526px -612px; +} + +.recurring-snapshots .ui-tabs ul li.monthly a { + background-position: -528px -656px; +} + +.recurring-snapshots .ui-tabs div.ui-tabs-panel { + height: 144px; + width: 576px; + border: none; + border-bottom: 1px solid #C3C3C3; + background: #E9E9E9; + margin: 0; + padding: 0; + /*+placement:shift -89px 0px;*/ + position: relative; + left: -89px; + top: 0px; +} + +.recurring-snapshots .ui-tabs div.ui-tabs-panel.ui-tabs-hide { + display: none; +} + +/*Upload volume*/ +.upload-volume { +} + +.upload-volume .list-view { + margin-top: 5px !important; +} + +.upload-volume .listView-container { + background: #FFFFFF; + width: 823px; + margin: 71px 0px 20px 28px; + border: 1px solid #DADADA; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; +} + +.upload-volume div.list-view .data-table div.fixed-header { + top: 115px !important; + left: 56px !important; + background: #FFFFFF !important; +} + +.upload-volume .data-table table.body { + margin-top: 66px !important; + margin-left: 19px; +} + +.upload-volume .list-view .toolbar { + top: 118px; + width: 801px; + left: 43px; + background: transparent; + border: none; +} + +.upload-volume .top-fields { + float: left; + clear: none; + margin-left: 24px; +} + +.upload-volume .top-fields .field { + float: left; + margin-right: 50px; +} + +.upload-volume .top-fields input { + float: right; + /*+border-radius:3px;*/ + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + -khtml-border-radius: 3px; + border-radius: 3px; + border-radius: 3px 3px 3px 3px; + padding: 2px; + width: 186px; +} + +.upload-volume .top-fields label, +.upload-volume .desc { + display: block; + float: left; + padding: 6px; + font-size: 12px; + color: #4C5D6C; + /*+text-shadow:0px 0px #FFFFFF;*/ + -moz-text-shadow: 0px 0px #FFFFFF; + -webkit-text-shadow: 0px 0px #FFFFFF; + -o-text-shadow: 0px 0px #FFFFFF; + text-shadow: 0px 0px #FFFFFF; +} + +.upload-volume .desc { + position: absolute; + width: 825px; + text-align: left; + top: 79px; + left: 32px; + border-top: 1px solid #CFCFCF; +} + +/*Network detail chat*/ +.network-chart { + width: 100%; + height: 100%; + position: relative; + background: url(../images/bg-network.png) no-repeat 38% 70px; +} + +.network-chart.static-nat { + background: url(../images/bg-network-nat.png) no-repeat 31% 62px; +} + +.network-chart ul { + width: 536px; + height: 421px; + position: absolute; + top: 0px; + left: 0px; +} + +.network-chart li { + display: block; + width: 147px; + height: 86px; + background: url(../images/buttons.png) no-repeat 0px -399px; +} + +.network-chart li.static-nat-enabled { + /*+placement:shift 31px 44px;*/ + position: relative; + left: 31px; + top: 44px; +} + +.network-chart li.static-nat-enabled .vmname { + /*+placement:shift 16px 41px;*/ + position: relative; + left: 16px; + top: 41px; + cursor: pointer; + max-width: 98px; + max-height: 21px; + padding: 7px; + font-size: 10px; + position: absolute; + overflow: hidden; + color: #485563; + font-weight: bold; + /*+text-shadow:0px 1px 1px #000000;*/ + -moz-text-shadow: 0px 1px 1px #000000; + -webkit-text-shadow: 0px 1px 1px #000000; + -o-text-shadow: 0px 1px 1px #000000; + text-shadow: 0px 1px 1px #000000; + background: url(../images/bg-gradients.png) repeat-x 2px -221px; + color: #FFFFFF; + /*+border-radius:9px;*/ + -moz-border-radius: 9px; + -webkit-border-radius: 9px; + -khtml-border-radius: 9px; + border-radius: 9px; + border-radius: 9px 9px 9px 9px; +} + +.network-chart li.static-nat-enabled .vmname:hover { + background-position: 0px -946px; +} + +.network-chart li.static-nat-enabled .name { + background: url(../images/sprites.png) no-repeat -6px -460px; +} + +.network-chart li.static-nat-enabled .name span { + font-size: 11px; + padding: 0 0 0 25px; +} + +.network-chart li.disabled { + /*+opacity:100%;*/ + filter: alpha(opacity=100); + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100); + -moz-opacity: 1; + opacity: 1; +} + +.network-chart li.firewall { + /*+placement:shift 282px 188px;*/ + position: relative; + left: 356px; + top: 188px; + position: absolute; +} + +.network-chart li.loadBalancing { + /*+placement:shift 167px 342px;*/ + position: relative; + left: 237px; + top: 342px; + position: absolute; +} + +.network-chart li.portForwarding { + /*+placement:shift 401px 342px;*/ + position: relative; + left: 480px; + top: 342px; + position: absolute; +} + +.network-chart li .name { + color: #4E5F6F; + width: 130px; + /*+text-shadow:0px 1px 1px #FCFCFC;*/ + -moz-text-shadow: 0px 1px 1px #FCFCFC; + -webkit-text-shadow: 0px 1px 1px #FCFCFC; + -o-text-shadow: 0px 1px 1px #FCFCFC; + text-shadow: 0px 1px 1px #FCFCFC; + /*+placement:shift 10px 11px;*/ + position: relative; + left: 10px; + top: 11px; +} + +.network-chart li.disabled .name { + color: #8695A5; + /*+placement:shift 5px 32px;*/ + position: relative; + left: 5px; + top: 32px; + text-decoration: line-through; + text-align: center; +} + +.network-chart li .view-details { + /*+placement:anchor-bottom-right 34px 19px;*/ + position: absolute; + right: 34px; + bottom: 19px; + cursor: pointer; + background: #F7F7F7; + background: rgb(247, 247, 247); + background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIxJSIgc3RvcC1jb2xvcj0iI2Y3ZjdmNyIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNlYWVhZWEiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+); + background: -moz-linear-gradient(top, rgba(247,247,247,1) 1%, rgba(234,234,234,1) 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(1%,rgba(247,247,247,1)), color-stop(100%,rgba(234,234,234,1))); + background: -webkit-linear-gradient(top, rgba(247,247,247,1) 1%,rgba(234,234,234,1) 100%); + background: -o-linear-gradient(top, rgba(247,247,247,1) 1%,rgba(234,234,234,1) 100%); + background: -ms-linear-gradient(top, rgba(247,247,247,1) 1%,rgba(234,234,234,1) 100%); + background: linear-gradient(to bottom, rgba(247,247,247,1) 1%,rgba(234,234,234,1) 100%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f7f7f7', endColorstr='#eaeaea',GradientType=0 ); + font-size: 11px; + padding: 8px 20px; + color: #000000; + border: 1px solid #A2A2A2; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; +} + +.network-chart li .view-details:hover { + background: #D5D5D5; + /*+text-shadow:0px 1px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px 1px #FFFFFF; + -o-text-shadow: 0px 1px 1px #FFFFFF; + text-shadow: 0px 1px 1px #FFFFFF; + /*+box-shadow:inset 0px 0px 4px #000000;*/ + -moz-box-shadow: inset 0px 0px 4px #000000; + -webkit-box-shadow: inset 0px 0px 4px #000000; + -o-box-shadow: inset 0px 0px 4px #000000; + box-shadow: inset 0px 0px 4px #000000; +} + +.network-chart li.disabled .view-details { + display: none; +} + +/*System Dashboard*/ +.system-dashboard { + height: 258px; + width: 962px; + display: block; + /*+border-radius:3px;*/ + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + -khtml-border-radius: 3px; + border-radius: 3px; + /*+box-shadow:inset 0px 0px 1px #FFFFFF;*/ + -moz-box-shadow: inset 0px 0px 1px #FFFFFF; + -webkit-box-shadow: inset 0px 0px 1px #FFFFFF; + -o-box-shadow: inset 0px 0px 1px #FFFFFF; + box-shadow: inset 0px 0px 1px #FFFFFF; + position: relative; + margin: 18px 0 0 15px; +} + +.system-dashboard.zone { + height: 609px; + background-position: 0px -1423px; +} + +.system-dashboard-view .toolbar { + position: relative; +} + +.system-dashboard .head { + color: #000000; + /*+text-shadow:0px 1px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px 1px #FFFFFF; + -o-text-shadow: 0px 1px 1px #FFFFFF; + text-shadow: 0px 1px 1px #FFFFFF; + text-indent: 11px; + padding: 0px 0 12px; + /*+box-shadow:0px 0px 1px #FFFFFF;*/ + -moz-box-shadow: 0px 0px 1px #FFFFFF; + -webkit-box-shadow: 0px 0px 1px #FFFFFF; + -o-box-shadow: 0px 0px 1px #FFFFFF; + box-shadow: 0px 0px 1px #FFFFFF; +} + +.project-view .system-dashboard .head { + color: #FFFFFF; + /*+text-shadow:0px -1px #000000;*/ + -moz-text-shadow: 0px -1px #000000; + -webkit-text-shadow: 0px -1px #000000; + -o-text-shadow: 0px -1px #000000; + text-shadow: 0px -1px #000000; + /*+box-shadow:none;*/ + -moz-box-shadow: none; + -webkit-box-shadow: none; + -o-box-shadow: none; + box-shadow: none; + padding-top: 14px; +} + +.system-dashboard .view-more, +.system-dashboard .view-all { + float: right; + margin: -4px 19px 0 0; + cursor: pointer; + font-size: 13px; + font-weight: 100; + background: #DADADA repeat-x 0px -735px; + background: rgb(234, 234, 234); + background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2VhZWFlYSIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNkNmQ2ZDYiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+); + background: -moz-linear-gradient(top, rgba(234,234,234,1) 0%, rgba(214,214,214,1) 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(234,234,234,1)), color-stop(100%,rgba(214,214,214,1))); + background: -webkit-linear-gradient(top, rgba(234,234,234,1) 0%,rgba(214,214,214,1) 100%); + background: -o-linear-gradient(top, rgba(234,234,234,1) 0%,rgba(214,214,214,1) 100%); + background: -ms-linear-gradient(top, rgba(234,234,234,1) 0%,rgba(214,214,214,1) 100%); + background: linear-gradient(to bottom, rgba(234,234,234,1) 0%,rgba(214,214,214,1) 100%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eaeaea', endColorstr='#d6d6d6',GradientType=0 ); + /*+border-radius:3px;*/ + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + -khtml-border-radius: 3px; + border-radius: 3px; + border-radius: 3px 3px 3px 3px; + border: 1px solid #B5B5B5; +} + +.system-dashboard .view-more:hover, +.system-dashboard .view-all:hover { + background-position: 0px -763px; + /*+box-shadow:inset 0px 1px 1px #000000;*/ + -moz-box-shadow: inset 0px 1px 1px #000000; + -webkit-box-shadow: inset 0px 1px 1px #000000; + -o-box-shadow: inset 0px 1px 1px #000000; + box-shadow: inset 0px 1px 1px #000000; + background: #C1C1C1; +} + +.system-dashboard .status_box .view-all { + /*+placement:shift 18px 110px;*/ + position: relative; + left: 18px; + top: 110px; + width: 83%; + position: absolute; + text-align: center; + padding: 8px 0; +} + +.system-dashboard .status_box { + font-size: 14px; + margin: 10px 0 0; + background: transparent; + border: none; +} + +.system-dashboard .status_box li { + height: 178px; + width: 228px; + padding: 0; + margin: 0 0 0 8px; + /*+border-radius:3px;*/ + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + -khtml-border-radius: 3px; + border-radius: 3px; + position: relative; + border: 1px solid #C6C6C6; + float: left; +} + +.system-dashboard.zone .status_box li { + margin-bottom: 8px; + height: 152px; + background-color: #F4F4F4; +} + +.system-dashboard.zone .status_box li .icon { + background: url(../images/infrastructure-icons.png) no-repeat 0px 0px; + padding: 65px 80px 5px; + /*+placement:shift 31px 19px;*/ + position: relative; + left: 51px; + top: 19px; + position: absolute; + /*+opacity:56%;*/ + filter: alpha(opacity=56); + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=56); + -moz-opacity: 0.56; + opacity: 0.56; +} + +.system-dashboard .status_box li span { +} + +.system-dashboard .status_box li span.label { + color: #CCCFD4; + font-size: 12px; +} + +.system-dashboard .status_box li span.total { + font-size: 25px; +} + +.system-dashboard .status_box li span.label { + color: #CCCFD4; + font-size: 12px; +} + +.system-dashboard .status_box li span.unit { + color: #C1C4C9; + font-size: 13px; +} + +.system-dashboard .status_box li span.header { + margin: 1px 0 0; + /*+placement:shift 13px 5px;*/ + position: relative; + left: 13px; + top: 13px; + font-weight: 100; +} + +.system-dashboard.zone .status_box li span.header { + font-size: 14px; + color: #4F4F4F; +} + +.system-dashboard .status_box li span.status { + font-size: 27px; + /*+placement:shift 13px 141px;*/ + position: relative; + left: 13px; + top: 141px; + position: absolute; + color: #25FF25; + /*+text-shadow:0px 1px 1px #000000;*/ + -moz-text-shadow: 0px 1px 1px #000000; + -webkit-text-shadow: 0px 1px 1px #000000; + -o-text-shadow: 0px 1px 1px #000000; + text-shadow: 0px 1px 1px #000000; +} + +.system-dashboard .status_box li span.instance.total { + /*+placement:shift 12px 32px;*/ + position: relative; + left: 12px; + top: 32px; + position: absolute; +} + +.system-dashboard .status_box li span.instance.label { + /*+placement:shift 15px 53px;*/ + position: relative; + left: 15px; + top: 53px; + position: absolute; +} + +.system-dashboard .status_box li span.vcpu-hours.total { + /*+placement:shift 13px 76px;*/ + position: relative; + left: 13px; + top: 76px; + position: absolute; +} + +.system-dashboard .status_box li span.vcpu-hours.label { + /*+placement:shift 14px 95px;*/ + position: relative; + left: 14px; + top: 95px; + position: absolute; +} + +.system-dashboard .status_box li span.gb-hours.total { + /*+placement:shift 106px 77px;*/ + position: relative; + left: 106px; + top: 77px; + position: absolute; +} + +.system-dashboard .status_box li span.gb-hours.label { + /*+placement:shift 106px 95px;*/ + position: relative; + left: 106px; + top: 95px; + position: absolute; +} + +.system-dashboard .status_box li span.overview.total { + font-size: 56px; + /*+placement:shift 9px 29px;*/ + position: relative; + left: 9px; + top: 29px; + position: absolute; + font-weight: 100; + color: #2B7DAF; + /*+text-shadow:0px -1px 2px #FFFFFF;*/ + -moz-text-shadow: 0px -1px 2px #FFFFFF; + -webkit-text-shadow: 0px -1px 2px #FFFFFF; + -o-text-shadow: 0px -1px 2px #FFFFFF; + text-shadow: 0px -1px 2px #FFFFFF; +} + +.system-dashboard .status_box li.capacity span.overview.total { + font-size: 32px; +} + +.system-dashboard .status_box li span.overview.label { + /*+placement:shift 52px 79px;*/ + position: relative; + left: 52px; + top: 79px; + position: absolute; +} + +.system-dashboard .status_box li span.used.total { + /*+placement:shift 14px 130px;*/ + position: relative; + left: 14px; + top: 130px; + font-size: 30px; + position: absolute; +} + +.system-dashboard .status_box li span.used.label { + /*+placement:shift 14px 153px;*/ + position: relative; + left: 14px; + top: 153px; + position: absolute; +} + +.system-dashboard .status_box li span.used.unit { + /*+placement:shift 67px 135px;*/ + position: relative; + left: 67px; + top: 135px; + position: absolute; +} + +.system-dashboard .status_box li span.available.unit { + /*+placement:shift 159px 135px;*/ + position: relative; + left: 159px; + top: 135px; + position: absolute; +} + +.system-dashboard .status_box li span.available.total { + /*+placement:shift 97px 130px;*/ + position: relative; + left: 97px; + top: 130px; + font-size: 30px; + position: absolute; +} + +.system-dashboard .status_box li span.available.label { + /*+placement:shift 97px 153px;*/ + position: relative; + left: 97px; + top: 153px; + position: absolute; +} + +.system-dashboard-view .socket-info { + width: 100%; + height: 239px; + overflow: auto; + float: left; + padding: 0; +} + +.system-dashboard-view .socket-info > .title { + padding: 8px; + font-size: 13px; +} + +.system-dashboard-view .socket-info ul { +} + +.system-dashboard-view .socket-info li { + width: 139px; + padding: 13px; + /*+border-radius:3px;*/ + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + -khtml-border-radius: 3px; + border-radius: 3px; + margin: 7px; + border: 1px solid #CCC; + background: #EFEFEF; + float: left; +} + +.system-dashboard-view .socket-info li > div { + text-decoration: none; + float: left; +} + +.system-dashboard-view .socket-info li .name { + width: 100%; + font-weight: 100; + margin-bottom: 13px; +} + +.system-dashboard-view .socket-info li .hosts, +.system-dashboard-view .socket-info li .sockets { + width: 54px; + /*[empty]color:;*/ +} + +.system-dashboard-view .socket-info li div .title { + color: #424242; + border: none; + font-size: 13px; + padding-bottom: 3px; +} + +.add-zone-resource .form-container { + height: auto !important; + display: inline-block; + overflow: visible; +} + +.add-zone-resource .form-container form { + display: inline-block; + height: auto; +} + +.add-zone-resource { +} + +.add-zone-resource .head { + width: 100%; + margin-bottom: 7px; + display: inline-block; + border-bottom: 1px solid #AFBDCA; + /*+box-shadow:0px 1px #FFFFFF;*/ + -moz-box-shadow: 0px 1px #FFFFFF; + -webkit-box-shadow: 0px 1px #FFFFFF; + -o-box-shadow: 0px 1px #FFFFFF; + box-shadow: 0px 1px #FFFFFF; +} + +.add-zone-resource .head span { + float: left; + font-size: 14px; + text-indent: 5px; + padding: 10px 0 18px; +} + +.add-zone-resource .head select { + float: left; + margin: -3px 0 6px 13px; + margin: 8px 0 0 9px; +} + +/** Infrastructure icons*/ +.system-dashboard.zone .status_box li.zones .icon { + background-position: -36px -105px; +} + +.system-dashboard.zone .status_box li.pods .icon { + background-position: -229px -105px; +} + +.system-dashboard.zone .status_box li.clusters .icon { + background-position: -411px -96px; +} + +.system-dashboard.zone .status_box li.hosts .icon { + background-position: -601px -102px; +} + +.system-dashboard.zone .status_box li.primary-storage .icon { + background-position: -32px -404px; + /*+placement:shift 37px 68px;*/ + position: relative; + left: 37px; + top: 68px; +} + +.system-dashboard.zone .status_box li.sockets .icon { + background-position: -14px -581px; +} + +.system-dashboard.zone .status_box li.secondary-storage .icon { + background-position: -216px -404px; + /*+placement:shift 37px 68px;*/ + position: relative; + left: 37px; + top: 68px; +} + +.system-dashboard.zone .status_box li.system-vms .icon { + background-position: -408px -399px; +} + +.system-dashboard.zone .status_box li.virtual-routers .icon { + background-position: -601px -400px; +} + +/*Projects +** View switcher*/ +#header .view-switcher { + font-size: 12px; + color: #55687B; + float: left; + margin: 11px 0 0 18px; +} + +#header div.view-switcher { + height: 39px; + background-position: 0px -5px; + /*+placement:shift 0px -10px;*/ + position: relative; + left: 0px; + top: -10px; + margin-right: 9px; + display: none; +} + +#header div.view-switcher.alt { + background-position: 0px -41px; +} + +#header div.view-switcher div { + float: left; + /*[empty]display:;*/ + width: 126px; + padding: 13px 0 0; + margin: 0; + text-indent: 17px; + position: relative; + /*+text-shadow:0px -1px 1px #2D2D2D;*/ + -moz-text-shadow: 0px -1px 1px #2D2D2D; + -webkit-text-shadow: 0px -1px 1px #2D2D2D; + -o-text-shadow: 0px -1px 1px #2D2D2D; + text-shadow: 0px -1px 1px #2D2D2D; +} + +#header div.view-switcher .select.active { + color: #FFFFFF; + font-weight: bold; + /*+text-shadow:0px -1px 1px #5B5B5B;*/ + -moz-text-shadow: 0px -1px 1px #5B5B5B; + -webkit-text-shadow: 0px -1px 1px #5B5B5B; + -o-text-shadow: 0px -1px 1px #5B5B5B; + text-shadow: 0px -1px 1px #5B5B5B; +} + +#header div.view-switcher div span.icon { + background: url(../images/icons.png) no-repeat; + width: 10px; + height: 10px; + padding: 0 19px 0 0; + top: 0; + /*+placement:shift -4px 0px;*/ + position: relative; + left: -4px; + top: 0px; +} + +#header div.view-switcher div.default-view span.icon { + background-position: -23px 0px; +} + +#header div.view-switcher div.project-view span.icon { + background-position: -24px 0px !important; +} + +#header div.view-switcher div.select span.icon { + background-position: -47px 0px; +} + +#header .view-switcher span { + padding: 0 13px 0 0; + background-repeat: repeat-x; +} + +#header .view-switcher select { + max-width: 120px; + margin: 6px 3px 0 -21px; + padding: 3px 0 4px; +} + +/*** View switcher (drop-down)*/ +.project-switcher, .domain-switcher { + float: left; + width: 223px; + padding: 9px 17px 0 19px; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; +} + +.project-switcher label, .domain-switcher label { + top: 29px; + color: #FFFFFF; + font-size: 13px; + float: left; + margin-right: 7px; + margin-top: 5px; +} + +.project-switcher select, .domain-switcher select { + width: 70%; + float: left; + margin-top: 0px; + border: 1px solid #393939; + /*+text-shadow:0px -1px 1px #373737;*/ + -moz-text-shadow: 0px -1px 1px #373737; + -webkit-text-shadow: 0px -1px 1px #373737; + -o-text-shadow: 0px -1px 1px #373737; + text-shadow: 0px -1px 1px #373737; + background: #515151; + font-size: 13px; + font-weight: 100; + color: #FFFFFF; +} + +/*** Select project*/ +.project-selector { + display: inline-block; +} + +.project-selector-dialog .ui-widget-content { + padding: 0 !important; +} + +.project-selector .toolbar { + width: 420px; + position: relative; + background: transparent; + border: none; + border-bottom: 1px solid #93A4B4; + /*+box-shadow:0px 2px #FFFFFF;*/ + -moz-box-shadow: 0px 2px #FFFFFF; + -webkit-box-shadow: 0px 2px #FFFFFF; + -o-box-shadow: 0px 2px #FFFFFF; + box-shadow: 0px 2px #FFFFFF; + -moz-box-shadow: 0px 2px 0px #FFFFFF; + -webkit-box-shadow: 0px 2px 0px #FFFFFF; + -o-box-shadow: 0px 2px 0px #FFFFFF; +} + +.project-selector .search input[type=text] { + /*+border-radius:3px;*/ + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + -khtml-border-radius: 3px; + border-radius: 3px; + border-radius: 3px 3px 3px 3px; + width: 192px; + height: 18px; + margin: 6px 0 0 105px; + /*+box-shadow:0px 1px 1px #FFFFFF;*/ + -moz-box-shadow: 0px 1px 1px #FFFFFF; + -webkit-box-shadow: 0px 1px 1px #FFFFFF; + -o-box-shadow: 0px 1px 1px #FFFFFF; + box-shadow: 0px 1px 1px #FFFFFF; + border: 1px solid #9DADBB; + float: left; +} + +.project-selector .search input[type=submit] { + display: block; + float: left; + border: none; + cursor: pointer; + margin: 6px 0 0; + background: url(../images/sprites.png) no-repeat -601px -328px; + width: 25px; + height: 22px; + border-left: 1px solid #283979; + /*+placement:shift -2px 0px;*/ + position: relative; + left: -2px; + top: 0px; + cursor: pointer; +} + +.project-selector .listing { + margin: 15px; + border: 1px solid #D0D0D0; + position: relative; +} + +.project-selector .listing .data { + width: 100%; + height: 275px; + background: #F2F0F0; + overflow: auto; + overflow-x: hidden; + margin: 18px 0 0; +} + +.project-selector .listing .data ul { + text-align: left; + font-size: 11px; +} + +.project-selector .listing .data ul li { + padding: 10px 0 10px 7px; + cursor: pointer; + font-size: 12px; +} + +.project-selector .listing .data ul li.odd { + background: #DFE1E3; +} + +.project-selector .listing .data ul li:hover { + background: #CBDDF3; + border-top: 1px solid #FFFFFF; + border-bottom: 1px solid #BABFD9; + padding: 9px 0 9px 7px; +} + +.project-selector .listing .header { + width: 379px; + background: url(../images/bg-gradients.png) repeat-x 0px -164px; + /*+text-shadow:0px 1px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px 1px #FFFFFF; + -o-text-shadow: 0px 1px 1px #FFFFFF; + text-shadow: 0px 1px 1px #FFFFFF; + text-align: left; + color: #4F6171; + font-size: 11px; + padding: 3px 2px 3px 7px; + border-bottom: 1px solid #FFFFFF; + position: absolute; + left: 0; +} + +.project-selector .button.cancel { + color: #808080; + background: #B6B6B6 url("../images/gradients.png") repeat 0 -480px; + border: 1px solid #AAAAAA; + border-radius: 4px 4px 4px 4px; + font-size: 13px; + font-weight: bold; + padding: 8px 20px; + float: none; + cursor: pointer; + color: #838181; + left: 170px; + top: -8px; + width: 54px; + margin: auto auto 17px; +} + +.project-selector .button.cancel:hover { + color: #3A3A3A; +} + +/*** Resource management*/ +.project-dashboard .resources { +} + +.project-dashboard .resources form { + background: #FFFFFF; + width: 87%; + /*+border-radius:11px;*/ + -moz-border-radius: 11px; + -webkit-border-radius: 11px; + -khtml-border-radius: 11px; + border-radius: 11px; + border-radius: 11px 11px 11px 11px; + padding: 26px; + margin-top: 17px; + margin-left: 22px; + /*+box-shadow:inset 0px 3px 4px #979797;*/ + -moz-box-shadow: inset 0px 3px 4px #979797; + -webkit-box-shadow: inset 0px 3px 4px #979797; + -o-box-shadow: inset 0px 3px 4px #979797; + box-shadow: inset 0px 3px 4px #979797; + display: inline-block; +} + +.project-dashboard .resources form .field { + width: 100%; + float: left; + clear: both; + margin: auto auto 30px; +} + +.project-dashboard .resources form label { + float: left; +} + +.project-dashboard .resources form input[type=text] { + float: right; + width: 176px; + font-size: 16px; + margin: 0 287px 0 0; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; + border: 1px solid #C6C6C6; + padding: 6px; +} + +.project-dashboard .resources form input[type=submit] { + display: block; + border: none; + background: transparent url(../images/bg-gradients.png) 0px -220px; + float: left; + padding: 9px 20px; + cursor: pointer; + color: #FFFFFF; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; + clear: both; +} + +.detail-view .project-dashboard .resources form { + width: 83%; + border-bottom: 1px solid #DBDBDB; +} + +.detail-view .project-dashboard .resources form .field input { + margin-right: 105px; +} + +/*** Dashboard*/ +.project-dashboard .toolbar { + position: relative; +} + +.project-dashboard .ui-tabs { + /*+placement:shift 10px -31px;*/ + position: relative; + left: 10px; + top: -31px; +} + +.project-view .project-dashboard .ui-tabs .multi-edit table td { + background: #EAEAEA; +} + +.project-dashboard-view .overview-area { + float: left; +} + +.project-dashboard-view .compute-and-storage .system-dashboard, +.project-dashboard-view .users .system-dashboard { + width: 510px; + height: 230px; + float: left; + background: #777E88; +} + +.project-dashboard-view .compute-and-storage .system-dashboard ul, +.project-dashboard-view .users .system-dashboard ul { + height: 162px; + margin: 14px 0 0; +} + +.project-dashboard-view .compute-and-storage .system-dashboard li, +.project-dashboard-view .users .system-dashboard li { + width: 156px; + height: 161px; + background: #3D4045; + color: #FFFFFF; +} + +.project-dashboard-view .compute-and-storage .system-dashboard li .icon, +.project-dashboard-view .users li .icon { + width: 100px; + height: 76px; + /*+placement:shift 27px 20px;*/ + position: relative; + left: 27px; + top: 20px; + position: absolute; + background: url(../images/sprites.png) no-repeat 2px -1039px; +} + +.project-dashboard-view .compute-and-storage .system-dashboard li.storage .icon { + background-position: -89px -1036px; +} + +.project-dashboard-view .compute-and-storage .system-dashboard li.bandwidth .icon { + background-position: -184px -1036px; +} + +.project-dashboard-view .compute-and-storage .system-dashboard li .overview { + width: 100%; + height: 53px; + position: relative; + margin: 81px 0 0; + color: #FFFFFF; + /*+text-shadow:0px 1px 1px #000000;*/ + -moz-text-shadow: 0px 1px 1px #000000; + -webkit-text-shadow: 0px 1px 1px #000000; + -o-text-shadow: 0px 1px 1px #000000; + text-shadow: 0px 1px 1px #000000; +} + +.project-dashboard-view .compute-and-storage .system-dashboard li.storage .overview .total { + font-size: 28px; + /*+placement:shift 30px 21px;*/ + position: relative; + left: 30px; + top: 21px; + position: absolute; +} + +.project-dashboard-view .compute-and-storage .system-dashboard li.storage .overview .label { + font-size: 13px; + color: #C3C1C1; + /*+placement:shift 91px 33px;*/ + position: relative; + left: 91px; + top: 33px; + position: absolute; +} + +.project-dashboard-view .compute-and-storage .system-dashboard li .overview .overview-item { + float: left; + margin: 12px 0 0 20px; +} + +.project-dashboard-view .compute-and-storage .system-dashboard li .overview .overview-item .total { + font-size: 24px; + font-weight: bold; +} + +.project-dashboard-view .compute-and-storage .system-dashboard li .overview .overview-item .label { + font-size: 11px; + margin: 4px 0 0; + color: #C7C7C7; +} + +.project-dashboard-view .compute-and-storage .system-dashboard li .overview .overview-item.running .label { + color: #2BFF2B; + /*[empty]background-position:;*/ +} + +.project-dashboard-view .users .system-dashboard { + width: 509px; + height: 100%; + clear: both; +} + +.project-dashboard-view .users .system-dashboard ul { + overflow-y: auto; +} + +.project-dashboard-view .users .system-dashboard li { + width: 86px; + height: 138px; + margin-bottom: 24px; + margin-left: 6px; +} + +.project-dashboard-view .users .system-dashboard li .icon { + background-position: -306px -1044px; + left: 16px; +} + +.project-dashboard-view .users .system-dashboard li .header { + width: 77px; + max-width: 77px; + /*+placement:shift 7px 110px;*/ + position: relative; + left: 7px; + top: 110px; + position: absolute; + text-align: center; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +/**** Info box*/ +.info-boxes { + float: right; + width: 233px; + height: 551px; + margin: 21px 5px 0 0; +} + +.info-boxes .info-box { + display: inline-block; + border: 1px solid #B3C3D0; + /*+box-shadow:inset 0px -1px 7px #A7A7A7;*/ + -moz-box-shadow: inset 0px -1px 7px #A7A7A7; + -webkit-box-shadow: inset 0px -1px 7px #A7A7A7; + -o-box-shadow: inset 0px -1px 7px #A7A7A7; + box-shadow: inset 0px -1px 7px #A7A7A7; + background: #FFFFFF; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; +} + +.info-boxes .info-box.events { + margin-top: 4px; + height: 323px; + width: 228px; +} + +.info-boxes .info-box.events ul { + max-height: 295px; + overflow: auto; + overflow-x: hidden; +} + +.info-boxes .info-box ul { + margin: 0 0 3px 2px; + height: auto; + display: inline-block; +} + +.info-boxes .info-box ul li { + width: 224px; + margin: 0 2px 0 0; + display: inline-block; + border-bottom: 1px solid #BDD2DF; + border-top: 1px solid #FFFFFF; +} + +.info-boxes .info-box ul li.odd { + background: #ECECEC; +} + +.info-boxes .info-box .button { + background: url(../images/bg-gradients.png) 0px -734px; + color: #FFFFFF; + border: 1px solid #82A3C7; + /*+text-shadow:0px 1px 1px #000000;*/ + -moz-text-shadow: 0px 1px 1px #000000; + -webkit-text-shadow: 0px 1px 1px #000000; + -o-text-shadow: 0px 1px 1px #000000; + text-shadow: 0px 1px 1px #000000; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; + /*+box-shadow:inset 0px 1px 1px #85ACC4;*/ + -moz-box-shadow: inset 0px 1px 1px #85ACC4; + -webkit-box-shadow: inset 0px 1px 1px #85ACC4; + -o-box-shadow: inset 0px 1px 1px #85ACC4; + box-shadow: inset 0px 1px 1px #85ACC4; + padding: 2px 6px 3px 3px; + font-size: 10px; + cursor: pointer; + font-weight: bold; + float: right; + margin: 0 14px 0 0; +} + +.info-boxes .info-box .button span { + /*+placement:shift 0px 2px;*/ + position: relative; + left: 0px; + top: 2px; + float: left; +} + +.info-boxes .info-box .title .button { + margin: 4px 6px 0 3px; +} + +.info-boxes .info-box .title .button span { + color: #FFFFFF; + font-size: 10px; + margin: 0; + padding: 0; + /*+placement:shift 1px 1px;*/ + position: relative; + left: 1px; + top: 1px; +} + +.info-boxes .info-box .button:hover { + background-position: 0px -766px; +} + +.info-boxes .info-box .button .arrow { + width: 16px; + height: 13px; + float: right; + /*+placement:shift 0px 0px;*/ + position: relative; + left: 0px; + top: 0px; + background: url(../images/sprites.png) no-repeat -455px -84px; +} + +.info-boxes .info-box ul li .total, +.info-boxes .info-box ul li .date { + width: 52px; + height: 36px; + float: left; + font-size: 24px; + color: #647C91; + border-right: 1px solid #BDD2DF; + /*+placement:shift;*/ + position: relative; + left: 0; + top: 0; + text-align: right; +} + +.info-boxes .info-box ul li .date { + font-size: 11px; + text-align: center; + margin: 1px 0 0; +} + +.info-boxes .info-box ul li .date span { + /*+placement:shift 0px 11px;*/ + position: relative; + left: 0px; + top: 11px; +} + +.info-boxes .info-box ul li .desc { + color: #606060; + font-size: 12px; + /*+placement:shift 5px 8px;*/ + position: relative; + left: 5px; + top: 8px; + display: inline-block; + padding-bottom: 13px; + max-width: 153px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.info-boxes .info-box ul li .total span { + /*+placement:shift -5px 7px;*/ + position: relative; + left: -5px; + top: 7px; +} + +.info-boxes .info-box .title { + height: 27px; + border-bottom: 1px solid #BDD2DF; +} + +.info-boxes .info-box .title span { + /*+placement:shift 8px 6px;*/ + position: relative; + left: 8px; + top: 6px; + font-size: 12px; + font-weight: bold; + color: #4E748C; +} + +/*** New project form*/ +.new-project { + display: inline-block; + margin: 0 0 20px 30px; +} + +.new-project form { + margin: 0; +} + +.ui-dialog .new-project { + text-align: left; +} + +.ui-dialog .new-project .add-by { + font-size: 12px; + color: #5E6D7D; + margin-left: 11px; +} + +.ui-dialog .new-project .add-by input { + margin-right: 8px; +} + +.ui-dialog .new-project .add-by label { + margin-right: 12px; +} + +.new-project .title { + color: #3497E6; + font-size: 26px; + /*+text-shadow:0px 1px 2px #D6D6D6;*/ + -moz-text-shadow: 0px 1px 2px #D6D6D6; + -webkit-text-shadow: 0px 1px 2px #D6D6D6; + -o-text-shadow: 0px 1px 2px #D6D6D6; + text-shadow: 0px 1px 2px #D6D6D6; + letter-spacing: 0px; + margin: 10px 0 32px; +} + +.new-project .field { + /*+text-shadow:0px 1px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px 1px #FFFFFF; + -o-text-shadow: 0px 1px 1px #FFFFFF; + text-shadow: 0px 1px 1px #FFFFFF; + width: 686px; + display: inline-block; + background: #DFDFDF; + margin: -2px 0 -4px auto; +} + +#new-project-review-tabs-resouces { + background: #D2D2D2; + height: 225px; +} + +.new-project .resources .ui-widget-content { + background: #FFFFFF; +} + +.new-project .resources .field { + height: 39px; + padding: 0; +} + +.new-project .resources .field input { +} + +.new-project .field span.value { + color: #475765; + /*+placement:shift 21px 20px;*/ + position: relative; + left: 21px; + top: 20px; +} + +.new-project .field label { + display: block; + width: 104px; + height: 59px; + color: #5B5B5B; + float: left; + background: #D2D2D2; + text-align: right; + padding: 20px 24px 0 0; +} + +.new-project .resources .field label { + font-size: 14px; + height: auto; + padding: 10px 14px 14px 40px; +} + +.new-project .field label.error { + color: #FF0000; + font-size: 9px; + /*+placement:displace 154px 29px;*/ + position: absolute; + margin-left: 154px; + margin-top: 29px; + background: transparent; + width: auto; + height: auto; +} + +.new-project .field input[type=text] { + background: #FFFFFF 0px 7px; + /*+border-radius:5px;*/ + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + -khtml-border-radius: 5px; + border-radius: 5px; + border-radius: 5px 5px 5px 5px; + font-size: 14px; + border: 1px solid #E2E1DF; + /*+box-shadow:inset 0px 1px #A1A1A1;*/ + -moz-box-shadow: inset 0px 1px #A1A1A1; + -webkit-box-shadow: inset 0px 1px #A1A1A1; + -o-box-shadow: inset 0px 1px #A1A1A1; + box-shadow: inset 0px 1px #A1A1A1; + -moz-box-shadow: inset 0px 1px 0px #A1A1A1; + -webkit-box-shadow: inset 0px 1px 0px #A1A1A1; + -o-box-shadow: inset 0px 1px 0px #A1A1A1; + width: 506px; + height: 20px; + margin: 17px 25px 0 0; + float: right; + border: 1px solid #C7C7C7; +} + +.new-project .resources .field input[type=text] { + margin: 6px 9px 0 0; +} + +.new-project .button.cancel { + color: #808080; + background: transparent; + font-size: 12px; + font-weight: bold; + float: left; + cursor: pointer; + color: #838181; + /*+placement:shift 488px 9px;*/ + position: relative; + left: 488px; + top: 9px; + left: 480px; + margin: 19px 0 0 40px; +} + +.new-project .button.cancel:hover { + color: #3A3A3A; +} + +.new-project input[type=submit], +.new-project .button.confirm { + display: inline-block; + height: 31px; + border: none; + /*+placement:float-right 63px 18px;*/ + float: right; + position: relative; + left: 63px; + top: 18px; + color: #FFFFFF; + /*+text-shadow:0px -1px 1px #465259;*/ + -moz-text-shadow: 0px -1px 1px #465259; + -webkit-text-shadow: 0px -1px 1px #465259; + -o-text-shadow: 0px -1px 1px #465259; + text-shadow: 0px -1px 1px #465259; + background: #0049FF url(../images/gradients.png) 0px -317px; + font-size: 13px; + font-weight: bold; + border: 1px solid #0069CF; + border-top: 1px solid #0070FC; + /*+border-radius:9px;*/ + -moz-border-radius: 9px; + -webkit-border-radius: 9px; + -khtml-border-radius: 9px; + border-radius: 9px; + border-radius: 9px 9px 9px 9px; + margin: 0px 63px 0 0; + cursor: pointer; +} + +.new-project input[type=submit]:hover, +.new-project .button.confirm:hover { + background-position: -3px -369px; +} + +.new-project .button { + cursor: pointer; +} + +.new-project .button.confirm { + display: block; + height: 27px; + padding: 13px 10px 0px 12px; +} + +.new-project .button.confirm.next { + padding: 10px 34px 0px 29px; +} + +.new-project .review .button.confirm.next { + /*+placement:shift 25px 11px;*/ + position: relative; + left: 25px; + top: 11px; +} + +.new-project .review .ui-tabs { + /*+placement:shift -29px -31px;*/ + position: relative; + left: -29px; + top: -31px; +} + +.new-project .review .ui-tabs .ui-widget-content { + width: 695px; + height: 185px; +} + +.new-project .review .ui-tabs .ui-widget-content.ui-tabs-hide { + display: none; +} + +.new-project .review .ui-tabs li { +} + +.new-project .review .ui-tabs ul { + text-align: left; + /*+placement:shift 0px -2px;*/ + position: relative; + left: 0px; + top: -2px; +} + +.new-project .review .ui-tabs .list-view { + width: 688px; + height: 185px !important; +} + +.new-project .review .ui-tabs .list-view .fixed-header { + position: absolute; + z-index: 1000; + height: 58px; + background: #FFFFFF; + top: -22px; +} + +.new-project .review .ui-tabs .list-view .data-table table { + width: 669px; + margin: 31px 0 -1px; +} + +.new-project .review .ui-tabs .list-view .data-table table .edit { + width: 132px; + background: #FFFFFF; + /*+placement:shift 14px 0px;*/ + position: relative; + left: 14px; + top: 0px; +} + +.new-project .review .ui-tabs .list-view .data-table table .edit select { + width: 95px; + height: 20px; + float: left; + background: #FFFFFF; + border: 1px solid #B2B2B2; +} + +.new-project .review .ui-tabs .list-view .data-table table .edit .action { + float: left; + margin: 0; + padding: 0; + /*+placement:shift 14px 0px;*/ + position: relative; + left: 14px; + top: 0px; + height: 20px; +} + +.new-project .review .ui-tabs .list-view .toolbar { + display: none; +} + +.new-project .review .ui-tabs .list-view .toolbar { +} + +.new-project .review .project-data { + padding: 16px; + background: #F4F4F4; + margin: 0; + /*+placement:shift -19px -13px;*/ + position: relative; + left: -19px; + top: -13px; +} + +.new-project .review .project-data .field { + width: 677px; + margin: auto; +} + +.new-project .button.later { + color: #808080; + background: url(../images/bg-gradients.png) 0px -261px; + border: 1px solid #B1B1B1; + /*+box-shadow:inset 0px 2px 2px #FFFFFF;*/ + -moz-box-shadow: inset 0px 2px 2px #FFFFFF; + -webkit-box-shadow: inset 0px 2px 2px #FFFFFF; + -o-box-shadow: inset 0px 2px 2px #FFFFFF; + box-shadow: inset 0px 2px 2px #FFFFFF; + float: right; + font-size: 13px; + margin: 19px -40px 0 0; + padding: 13px 7px 14px 8px; + /*+border-radius:10px;*/ + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + -khtml-border-radius: 10px; + border-radius: 10px; + border-radius: 10px 10px 10px 10px; +} + +.new-project .button.later:hover { + color: #000000; + /*+box-shadow:inset 0px 1px 1px #A1A1A1;*/ + -moz-box-shadow: inset 0px 1px 1px #A1A1A1; + -webkit-box-shadow: inset 0px 1px 1px #A1A1A1; + -o-box-shadow: inset 0px 1px 1px #A1A1A1; + box-shadow: inset 0px 1px 1px #A1A1A1; + background-position: 0px -86px; +} + +.new-project input[type=submit]:hover { + background-position: -3px -369px; +} + +.new-project .resources input[type=submit] { + display: none; +} + +.new-project .multi-edit { + width: 671px; +} + +.new-project .multi-edit .data { + width: 700px; +} + +.new-project .multi-edit .data .data-item { + margin: 0; + border: none; + border: 1px solid #D2D2D2; +} + +.new-project .multi-edit .data .data-item.even td { + background: #DFE1E3; +} + +/*Tooltip*/ +.tooltip-box { + width: 15%; + height: auto; + display: inline-block; + padding: 4px; + background: #FFFFFF; + border: 1px solid #BEB8B8; + padding: 10px; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + margin-left: 23px; + /*+box-shadow:0px 1px 12px #353535;*/ + -moz-box-shadow: 0px 1px 12px #353535; + -webkit-box-shadow: 0px 1px 12px #353535; + -o-box-shadow: 0px 1px 12px #353535; + box-shadow: 0px 1px 12px #353535; +} + +.tooltip-box .arrow { + width: 19px; + height: 30px; + background: url(../images/sprites.png) -585px -947px; + /*+placement:shift -16px 3px;*/ + position: relative; + left: -16px; + top: 3px; + position: absolute; +} + +/*Tagger*/ +.tagger { + width: 94%; + margin: auto; + padding-bottom: 12px; + background: #F2F0F0; + border: 1px solid #CFC9C9; + /*+placement:shift -4px 0px;*/ + position: relative; + left: -4px; + top: 0px; +} + +.tagger .field { + width: 35%; + float: left; + position: relative; +} + +.tagger .tag-info { + font-size: 11px; + color: #757575; + margin-top: 12px; + margin-left: 8px; +} + +.tagger .tag-info.title { + font-size: 11px; + color: #6F9BF0; + margin-bottom: 5px; +} + +.tagger form { + margin: 12px 9px 0px; +} + +.tagger.readonly form { + display: none; +} + +.tagger form label { + display: block; + float: left; + width: 25px; + text-align: right; + font-size: 10px; + color: #394552; + margin-right: 9px; + /*+placement:shift 5px 8px;*/ + position: relative; + left: 5px; + top: 8px; +} + +.tagger form label.error { + position: absolute; + color: #FF0000; + left: 44px; + top: 28px !important; + /*[empty]background-color:;*/ +} + +.tagger form input { + padding: 4px; + background: #FFFFFF; + border: 1px solid #808080; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; +} + +.tagger form input { + width: 45%; + margin-left: 9px; +} + +.tagger form input[type=submit] { + background: url(../images/bg-gradients.png) repeat-x 0px -220px; + cursor: pointer; + color: #FFFFFF; + /*+text-shadow:0px -1px 2px #000000;*/ + -moz-text-shadow: 0px -1px 2px #000000; + -webkit-text-shadow: 0px -1px 2px #000000; + -o-text-shadow: 0px -1px 2px #000000; + text-shadow: 0px -1px 2px #000000; + border: none; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + padding: 7px 25px 7px 26px; + margin-left: 16px; + width: auto; +} + +.tagger form input[type=submit]:hover { + background-position: 0px -946px; +} + +.tagger ul { + display: block; + width: 96%; + margin: 16px auto auto; + /*+border-radius:2px;*/ + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + -khtml-border-radius: 2px; + border-radius: 2px; + overflow: auto; + padding-bottom: 10px; + border: 1px solid #D2D2D2; + background: #FFFFFF; + /*+box-shadow:inset 0px 0px 10px #DCDCDC;*/ + -moz-box-shadow: inset 0px 0px 10px #DCDCDC; + -webkit-box-shadow: inset 0px 0px 10px #DCDCDC; + -o-box-shadow: inset 0px 0px 10px #DCDCDC; + box-shadow: inset 0px 0px 10px #DCDCDC; +} + +.tagger.readonly ul { +} + +.tagger ul li { + background: #DFDFDF 0px 4px; + height: 15px; + padding: 0px 18px 0 7px; + display: inline-block; + float: left; + margin-left: 7px; + margin-right: 2px; + margin-top: 5px; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + /*+placement:shift 0px 2px;*/ + position: relative; + left: 0px; + top: 2px; +} + +.tagger ul li span { + color: #000000; +} + +.tagger ul li span.label span.value { + max-width: 100px; + overflow: hidden; +} + +.tagger ul li span.label { + font-size: 10px; + position: relative; + left: 15px; + top: -2px; +} + +.tagger.readonly ul li span.label { + left: 6px; +} + +.tagger ul li span.label > span { + float: left; + display: block; + margin-top: 2px; +} + +.tagger ul li span.label > span.key { + font-weight: bold; + max-width: 134px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + margin-left: 15px; + margin-right: 5px; +} + +.tagger ul li span.label > span.value { + max-width: 160px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + margin-left: 6px; +} + +.tagger ul li span.remove { + width: 15px !important; + overflow: hidden !important; + height: 11px !important; + background: #DFDFDF; + display: block; + top: 0px !important; + left: -3px !important; + text-indent: 4px; + padding: 4px 0px 0px 8px; + font-size: 8px; + font-weight: bold; + cursor: pointer; + position: absolute !important; + color: #5B5B5B; +} + +.tagger.readonly ul li span.remove { + display: none; +} + +.tagger ul li span.remove:hover { + color: #000000; +} + +/** Dialog tagger*/ +.ui-dialog .tagger { + width: 375px; +} + +.ui-dialog .tagger .tag-info { + display: none; +} + +.ui-dialog.editTags .ui-button { + float: right; +} + +.ui-dialog.editTags .ui-dialog-buttonpane { + float: right; +} + +.ui-dialog .tagger .field { + width: 119px !important; +} + +.ui-dialog .tagger input.key, +.ui-dialog .tagger input.value { + width: 66px !important; + height: 15px; + font-size: 11px !important; +} + +.ui-dialog .tagger input[type=submit] { + padding: 6px 15px; +} + +/*VPC / vApps*/ +.vpc-chart { + width: 100%; + height: 94%; + overflow: auto; + position: relative; + margin: 30px 0 0; + background: #FFFFFF 0px 24px; +} + +.vpc-chart .vpc-title { + width: 210px; + font-size: 22px; + /*+placement:shift 11px 41px;*/ + position: relative; + left: 11px; + top: 41px; + position: absolute; + color: #5F768A; +} + +.vpc-chart .vpc-title > span { + max-width: 160px; + display: block; + float: left; + overflow-y: hidden; + overflow-x: auto; +} + +.vpc-chart .vpc-title .icon { + padding: 7px 15px; + background: url(../images/sprites.png) no-repeat -145px -195px; + margin-left: 10px; + cursor: pointer; + /*+placement:shift 6px -8px;*/ + position: relative; + left: 6px; + top: -8px; + float: left; +} + +.vpc-chart .vpc-title .vpc-configure-tooltip { + width: 129px; + padding: 35px 10px 10px; + font-size: 14px; + display: none; + z-index: 2000; + position: absolute; +} + +.vpc-chart .vpc-title .vpc-configure-tooltip .arrow { + width: 30px; + height: 20px; + background: #FFFFFF url(../images/sprites.png) no-repeat -589px -997px; + /*+placement:shift 13px 26px;*/ + position: relative; + left: 13px; + top: 26px; + position: absolute; + z-index: 1; +} + +.vpc-chart .vpc-title .vpc-configure-tooltip ul { + border: 1px solid #C2C2C2; + background: #FFFFFF; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + /*+placement:shift 0px -6px;*/ + position: relative; + left: 0px; + top: -6px; + margin: 10px 0; + padding: 9px; + /*+box-shadow:0px 1px 8px #CBCBCB;*/ + -moz-box-shadow: 0px 1px 8px #CBCBCB; + -webkit-box-shadow: 0px 1px 8px #CBCBCB; + -o-box-shadow: 0px 1px 8px #CBCBCB; + box-shadow: 0px 1px 8px #CBCBCB; +} + +.vpc-chart .vpc-title .vpc-configure-tooltip li { + padding: 3px 0 5px; + cursor: pointer; + font-size: 12px; +} + +.vpc-chart .vpc-title .vpc-configure-tooltip li:hover { + font-weight: bold; +} + +.vpc-chart ul.tiers { + padding: 0 0 0 26px; + margin: 79px 0 0 232px; + border-left: 3px solid #CCC; +} + +.vpc-chart li.tier { + display: block; + width: 258px; + height: 107px; + margin: -55px 0 90px; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border: 1px solid #50545A; + background: url(../images/bg-gradients.png) 0px -2637px; + /*+placement:shift 0px 58px;*/ + position: relative; + left: 0px; + top: 58px; + position: relative; + /*+box-shadow:0px 5px 7px #DADADA;*/ + -moz-box-shadow: 0px 5px 7px #DADADA; + -webkit-box-shadow: 0px 5px 7px #DADADA; + -o-box-shadow: 0px 5px 7px #DADADA; + box-shadow: 0px 5px 7px #DADADA; +} + +.vpc-chart li.tier .loading-overlay { + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; +} + +.vpc-chart li.tier .connect-line { + position: absolute; + width: 28px; + height: 3px; + background: #CCCCCC 0px -8px; + /*+placement:shift -29px 49px;*/ + position: relative; + left: -29px; + top: 49px; + position: absolute; +} + +.vpc-chart li.tier span.title { + color: #FFFFFF; + /*+placement:shift 8px 7px;*/ + position: relative; + left: 8px; + top: 7px; + position: absolute; + cursor: pointer; + padding: 3px; + font-size: 24px; + /*+text-shadow:1px 2px 2px #000000;*/ + -moz-text-shadow: 1px 2px 2px #000000; + -webkit-text-shadow: 1px 2px 2px #000000; + -o-text-shadow: 1px 2px 2px #000000; + text-shadow: 1px 2px 2px #000000; + text-decoration: underline; +} + +.vpc-chart li.tier span.cidr { + /*+placement:shift 12px 46px;*/ + position: relative; + left: 12px; + top: 46px; + position: absolute; + font-size: 14px; + /*+text-shadow:0px -1px 1px #343E4C;*/ + -moz-text-shadow: 0px -1px 1px #343E4C; + -webkit-text-shadow: 0px -1px 1px #343E4C; + -o-text-shadow: 0px -1px 1px #343E4C; + text-shadow: 0px -1px 1px #343E4C; + color: #FFFFFF; +} + +.vpc-chart li.tier .actions { + width: 258px; + height: 35px; + background: #CCC; + /*+border-radius:0 0 4px 4px;*/ + -moz-border-radius: 0 0 4px 4px; + -webkit-border-radius: 0 0 4px 4px; + -khtml-border-radius: 0 0 4px 4px; + border-radius: 0 0 4px 4px; + /*+placement:shift -1px 71px;*/ + position: relative; + left: -1px; + top: 71px; + position: absolute; + /*+box-shadow:inset 0px 1px #FFFFFF;*/ + -moz-box-shadow: inset 0px 1px #FFFFFF; + -webkit-box-shadow: inset 0px 1px #FFFFFF; + -o-box-shadow: inset 0px 1px #FFFFFF; + box-shadow: inset 0px 1px #FFFFFF; + border: 1px solid #808080; + border-top: 1px solid #4C545E; + position: absolute; +} + +.vpc-chart li.tier .actions .action { + cursor: pointer; + float: left; + width: 50px; + height: 24px; + text-align: center; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + margin: 4px 0px 4px 4px; + border: 1px solid #909090; + color: #4B637A; + font-weight: bold; + /*+text-shadow:0px 1px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px 1px #FFFFFF; + -o-text-shadow: 0px 1px 1px #FFFFFF; + text-shadow: 0px 1px 1px #FFFFFF; + background: url(../images/bg-gradients.png) 0px -2533px; +} + +.vpc-chart li.tier .actions .action.disabled, +.vpc-chart li.tier .actions .action.disabled:hover { + color: #9D9D9D; + background: #CFCFCF; + /*+text-shadow:none;*/ + -moz-text-shadow: none; + -webkit-text-shadow: none; + -o-text-shadow: none; + text-shadow: none; + border-color: #B5B5B5; + cursor: not-allowed; + /*+box-shadow:none;*/ + -moz-box-shadow: none; + -webkit-box-shadow: none; + -o-box-shadow: none; + box-shadow: none; +} + +.vpc-chart li.tier .actions .action:hover { + border: 1px solid #7A8B9A; + background-position: 0px -106px; + color: #5B7A96; + /*+box-shadow:inset 1px 2px 4px #808080;*/ + -moz-box-shadow: inset 1px 2px 4px #808080; + -webkit-box-shadow: inset 1px 2px 4px #808080; + -o-box-shadow: inset 1px 2px 4px #808080; + box-shadow: inset 1px 2px 4px #808080; +} + +.vpc-chart li.tier .actions .action span.label { + /*+placement:shift 1px 3px;*/ + position: relative; + left: 1px; + top: 3px; + font-size: 11px; +} + +.vpc-chart li.tier .actions .action.remove, +.vpc-chart li.tier .actions .action.remove:hover { + background: none; + width: 30px; + padding: 0; + float: right; + border: none; + /*+placement:shift -3px -2px;*/ + position: relative; + left: -3px; + top: -2px; + /*+box-shadow:none;*/ + -moz-box-shadow: none; + -webkit-box-shadow: none; + -o-box-shadow: none; + box-shadow: none; +} + +.vpc-chart li.tier .action span.icon { + background-image: url(../images/sprites.png); + cursor: pointer; + width: 37px; + height: 23px; + float: left; + /*+placement:shift 1px 3px;*/ + position: relative; + left: 1px; + top: 3px; +} + +.vpc-chart li.tier .vm-count { + font-size: 23px; + left: 134px; + top: 3px; + position: absolute; + color: #FFFFFF; + /*+text-shadow:1px 2px 2px #000000;*/ + -moz-text-shadow: 1px 2px 2px #000000; + -webkit-text-shadow: 1px 2px 2px #000000; + -o-text-shadow: 1px 2px 2px #000000; + text-shadow: 1px 2px 2px #000000; + cursor: pointer; + display: block; + padding: 5px; + text-align: center; + width: 100px; + border: 1px solid transparent; + margin: 4px; + text-decoration: underline; +} + +.vpc-chart li.tier.loading .vm-count { + padding-right: 10px; +} + +.vpc-chart li.tier .vm-count .loading-overlay { + display: none; + width: 24px; + height: 24px; + position: absolute; + left: 15px; + top: 7px; + /*+border-radius:12px;*/ + -moz-border-radius: 12px; + -webkit-border-radius: 12px; + -khtml-border-radius: 12px; + border-radius: 12px; + background-image: url(../images/ajax-loader-small.gif); + /*+opacity:100%;*/ + filter: alpha(opacity=100); + -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100); + -moz-opacity: 1; + opacity: 1; +} + +.vpc-chart li.tier.loading .vm-count .loading-overlay { + display: block; +} + +.vpc-chart li.tier .vm-count:hover, +.vpc-chart li.tier .title:hover { + border-radius: 4px; + border: 1px solid #4C545E; + background: url(../images/bg-gradients.png) 0px -2751px; +} + +.vpc-chart li.tier .vm-count .total { + padding-right: 4px; + font-size: 24px; +} + +.vpc-chart li.tier.placeholder { + background: #ECECEC; + border: dotted #ACACAC; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + /*+box-shadow:none;*/ + -moz-box-shadow: none; + -webkit-box-shadow: none; + -o-box-shadow: none; + box-shadow: none; + cursor: pointer; +} + +.vpc-chart li.tier.placeholder:hover { + background: #D3D3D3; + /*+box-shadow:0px 2px 8px #A7A7A7;*/ + -moz-box-shadow: 0px 2px 8px #A7A7A7; + -webkit-box-shadow: 0px 2px 8px #A7A7A7; + -o-box-shadow: 0px 2px 8px #A7A7A7; + box-shadow: 0px 2px 8px #A7A7A7; +} + +.vpc-chart li.tier.placeholder span { + top: 40px; + left: 66px; + color: #9F9F9F; + /*+text-shadow:none;*/ + -moz-text-shadow: none; + -webkit-text-shadow: none; + -o-text-shadow: none; + text-shadow: none; + text-decoration: none; +} + +.vpc-chart li.tier.placeholder:hover span { + color: #000000; + /*+text-shadow:0px 0px 7px #FFFFFF;*/ + -moz-text-shadow: 0px 0px 7px #FFFFFF; + -webkit-text-shadow: 0px 0px 7px #FFFFFF; + -o-text-shadow: 0px 0px 7px #FFFFFF; + text-shadow: 0px 0px 7px #FFFFFF; + background: none; + border: none; +} + +.vpc-chart li.tier.virtual-router { + margin: 0px; + width: 222px; + height: 65px; + /*+placement:shift 17px -36px;*/ + position: relative; + left: 17px; + top: -36px; + background-position: 0px -2519px; + border: 1px solid #ADADAD; + cursor: pointer; +} + +.vpc-chart li.tier.virtual-router:hover { + text-decoration: underline; +} + +.vpc-chart li.tier.virtual-router.disabled:hover { + text-decoration: none; +} + +.vpc-chart li.tier.virtual-router.disabled, +.vpc-chart li.tier.virtual-router.disabled span { + cursor: default; +} + +.vpc-chart li.tier.virtual-router span { + color: #586E82; + font-size: 18px; + /*+text-shadow:0px 1px 3px #FFFFFF;*/ + -moz-text-shadow: 0px 1px 3px #FFFFFF; + -webkit-text-shadow: 0px 1px 3px #FFFFFF; + -o-text-shadow: 0px 1px 3px #FFFFFF; + text-shadow: 0px 1px 3px #FFFFFF; + /*+placement:shift 53px 22px;*/ + position: relative; + left: 53px; + top: 22px; + text-decoration: none; +} + +.vpc-chart li.tier.virtual-router span:hover { + background: none; + border: none; +} + +.vpc-chart li.tier.virtual-router .connect-line { + /*+placement:shift -47px 14px;*/ + position: relative; + left: -47px; + top: 14px; + width: 46px; +} + +/*VPC: Enable Static NAT fields*/ +.list-view.instances .filters.tier-select { + width: 246px; + padding: 2px 20px 2px 13px; + margin: 1px 120px 0 19px; +} + +.list-view.instances .filters.tier-select label { + color: #FFFFFF; + /*+text-shadow:0px 1px 3px #000000;*/ + -moz-text-shadow: 0px 1px 3px #000000; + -webkit-text-shadow: 0px 1px 3px #000000; + -o-text-shadow: 0px 1px 3px #000000; + text-shadow: 0px 1px 3px #000000; +} + +.list-view.instances .filters.tier-select select { + width: 166px; + float: left; +} + +/*Configure ACL dialog / VM tier list view dialog*/ +.ui-dialog.configure-acl .multi-edit { + width: 866px; +} + +.ui-dialog.configure-acl .multi-edit table { + max-width: none; +} + +.ui-dialog.configure-acl .multi-edit table select, +.detail-view .acl .multi-edit select { + width: 76px; +} + +.ui-dialog.configure-acl .ui-dialog-buttonpane { + /*+placement:shift 722px -2px;*/ + position: relative; + left: 722px; + top: -2px; +} + +.ui-dialog.configure-acl div.view.list-view { + max-height: 474px; +} + +.ui-dialog.configure-acl .multi-edit .data { + width: 901px; + padding: 0; + margin: 0; + height: 370px; + overflow: auto; + overflow-x: hidden; +} + +.ui-dialog.configure-acl .multi-edit .data .multi-actions { + min-width: none !important; + max-width: none !important; +} + +.ui-dialog.configure-acl .view.list-view table.body tr td.actions { + width: 184px !important; + max-width: 184px !important; +} + +div.ui-dialog div.acl div.multi-edit table.multi-edit thead tr th, +div.ui-dialog div.acl div.multi-edit table.multi-edit tbody tr td { + min-width: 75px; +} + +div.ui-dialog div.acl div.multi-edit div.data div.data-body div.data-item table tbody tr td { + width: 100%; + min-width: 77px; +} + +.detail-view .acl .multi-edit { +} + +.detail-view .acl .multi-edit th, +.detail-view .acl .multi-edit td { + padding-right: 0px !important; + min-width: 25px !important; +} + +.detail-view .acl .multi-edit th { + font-size: 10px; +} + +.detail-view .acl .multi-edit input { + width: 50px; +} + +.detail-view .acl .multi-edit .add-vm { + width: 51px; + text-indent: 0px; + padding-right: 0px; +} + +.detail-view .acl .multi-edit td.multi-actions { + width: 65px; +} + +/*HEALTH CHECK*/ +.ui-dialog .health-check { + height: 295px !important; + padding-bottom: 93px; +} + +div.ui-dialog div.health-check div.health-check-description { + color: #808080; +} + +div.ui-dialog div.health-check div.form-container form div.form-item { + width: 58%; + margin-left:116px; + margin-top: -16px; + margin-bottom: 30px; +} + +div.ui-dialog div.health-check div.health-check-config-title { + float: left; + color: #808080; + font-size: 17px; + margin-left: 15px; +} + +div.ui-dialog div.health-check div.health-check-advanced-title { + float: left; + color: #808080; + font-size: 17px; + margin-left: 15px; + margin-top: -70px; +} + +/*Autoscaler*/ +.ui-dialog div.autoscaler { + overflow: auto; + 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 { +} + +.ui-dialog div.autoscaler .detail-actions .buttons { + float: right; + margin-right: 6px; +} + +.ui-dialog div.autoscaler .detail-actions .buttons .action { + width: 32px; + float: left; +} + +.ui-dialog div.autoscaler div.form-container div.form-item[rel=securityGroups] { + display: block; + width: 370px; + float: left; +} + +.ui-dialog div.autoscaler div.form-container div.form-item[rel=diskOfferingId] { + display: inline-block; + width: 370px; + float: left; + position: relative; + margin-top: 1px; +} + +.ui-dialog div.autoscaler div.form-container div.form-item[rel=minInstance] { + display: block; + width: 50%; + float: left; +} + +.ui-dialog div.autoscaler div.form-container div.form-item[rel=maxInstance] { + display: inline-block; + width: 50%; + float: left; + left: -30px; + position: relative; +} + +.ui-dialog div.autoscaler div.form-container div.form-item[rel=interval] { + display: block; + width: 50%; + float: left; +} + +.ui-dialog div.autoscaler div.form-container div.form-item[rel=quietTime] { + display: inline-block; + width: 50%; + float: left; + left: -15px; + position: relative; +} + +.ui-dialog div.autoscaler div.form-container div.form-item[rel=snmpCommunity] { + display: block; + width: 50%; + float: left; +} + +.ui-dialog div.autoscaler div.form-container div.form-item[rel=snmpPort] { + display: inline-block; + width: 50%; + float: left; + left: -15px; + position: relative; +} + +.ui-dialog div.autoscaler div.form-container div.value select { + width: 88%; + float: left; +} + +div.ui-dialog div.autoscaler div.scale-up-policy-title div.form-container { + height: 55px; +} + +div.ui-dialog div.autoscaler div.scale-down-policy-title div.form-container { + height: 55px; +} + +div.ui-dialog div.autoscaler div.scale-up-policy div.multi-edit { + margin-top: 0px; +} + +div.ui-dialog div.autoscaler div.scale-down-policy div.multi-edit { + margin-top: 0px; +} + +div.ui-dialog div.autoscaler div.scale-up-policy-title { + color: #0055BB; + margin-left: -650px; + margin-top: 40px; +} + +div.ui-dialog div.autoscaler div.scale-up-policy-title label { + font-size: 13px; + margin-left: 200px; + margin-right: 10px; +} + +div.ui-dialog div.autoscaler div.scale-up-policy-title hr.policy-divider { + border-left: 1px none #38546D; + border-right: 1px none #16222C; + border-top: 1px none #38546D; + margin-bottom: 12px; +} + +div.ui-dialog div.autoscaler div.scale-down-policy-title hr.policy-divider { + border-left: 1px none #38546D; + border-right: 1px none #16222C; + border-top: 1px none #38546D; + margin-bottom: 12px; +} + +div.ui-dialog div.autoscaler div.field-group.bottom-fields hr.policy-divider { + border-left: 1px none #38546D; + border-right: 1px none #16222C; + border-top: 1px none #38546D; + margin-top: 15px; + margin-bottom: -1px; +} + +div.ui-dialog div.autoscaler div.scale-down-policy-title label { + font-size: 13px; + margin-left: 170px; + margin-right: 10px; +} + +div.ui-dialog div.autoscaler div.scale-down-policy-title { + color: #0055BB; + margin-left: -620px; + margin-top: 10px; +} + +div.ui-dialog div.autoscaler div.scale-up-policy-title div.form-container div.form-item div.value input[type=text] { + margin-left: 729px; + width: 30%; + margin-top: -17px; +} + +div.ui-dialog div.autoscaler div.scale-up-policy-title div.form-container div.form-item div.name { + margin-left: 420px; +} + +div.ui-dialog div.autoscaler div.scale-down-policy-title div.form-container div.form-item div.value input[type=text] { + margin-left: 698px; + width: 30%; + margin-top: -16px; +} + +div.ui-dialog div.autoscaler div.scale-down-policy-title div.form-container div.form-item div.name { + margin-left: 420px; +} + +div.ui-dialog div.autoscaler div.scale-up-policy div.multi-edit div.data div.data-body div.data-item { + margin-bottom: 0px; + margin-right: 22px; +} + +div.ui-dialog div.autoscaler div.scale-down-policy div.multi-edit div.data div.data-body div.data-item { + margin-bottom: 0px; + margin-right: 22px; +} + +div.ui-dialog div.autoscaler div.scale-up-policy div.slide-label { + color: #A5A3A7; + font-size: 14px; + margin-bottom: 3px; + margin-left: 755px; + width: 12px; +} + +div.ui-dialog div.autoscaler div.scale-down-policy div.slide-label { + color: #A5A3A7; + font-size: 14px; + margin-bottom: 3px; + margin-left: 755px; + width: 12px; +} + +div.ui-dialog div.autoscaler div.scale-up-policy div.hide { + background: #FFFFFF url("../images/minus.png") no-repeat 38% 59%; + border: 1px solid #D0D0D0; + border-radius: 9px 9px 9px 9px; + cursor: pointer; + float: right; + height: 15px; + margin: -20px 45px 0 11px; + width: 14px; +} + +div.ui-dialog div.autoscaler div.scale-down-policy div.hide { + background: #FFFFFF url("../images/minus.png") no-repeat 31% 54%; + border: 1px solid #D0D0D0; + border-radius: 9px 9px 9px 9px; + cursor: pointer; + float: right; + height: 15px; + margin: -20px 45px 0 11px; + width: 14px; +} + +div.ui-dialog div.autoscaler div.scale-up-policy div.expand { + background: #FFFFFF url("../images/sprites.png") repeat -541px -499px; + border: 1px solid #D0D0D0; + border-radius: 9px 9px 9px 9px; + cursor: pointer; + float: right; + height: 15px; + margin: -20px 45px 0 11px; + width: 14px; +} + +div.ui-dialog div.autoscaler div.scale-down-policy div.expand { + background: #FFFFFF url("../images/sprites.png") repeat -541px -499px; + border: 1px solid #D0D0D0; + border-radius: 9px 9px 9px 9px; + cursor: pointer; + float: right; + height: 15px; + margin: -20px 45px 0 11px; + width: 14px; +} + +div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-item div.name label { + font-size: 11px; +} + +/*List state BG colors*/ +.list-view .body td.item-state-on { + background: #C0FFC0; + border-bottom: 1px solid #09BC09; +} + +.list-view .body td.item-state-off { + background: #FFD8CF; + border-bottom: 1px solid #FF9F9F; +} + +.list-view .body tr.selected td.item-state-on, +.list-view .body tr.selected td.item-state-off { + background-color: inherit; + border-color: inherit; +} + +/*Autoscaler*/ +.ui-dialog div.autoscaler { + overflow: auto; + 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: 72px; + font-size: 10px; +} + +.ui-dialog div.autoscaler .detail-actions { +} + +.ui-dialog div.autoscaler .detail-actions .buttons { + float: right; + margin-right: 6px; +} + +.ui-dialog div.autoscaler .detail-actions .buttons .action { + width: 32px; + float: left; +} + +.ui-dialog div.autoscaler div.form-container div.form-item[rel=securityGroups] { + display: block; + width: 370px; + float: left; +} + +.ui-dialog div.autoscaler div.form-container div.form-item[rel=diskOfferingId] { + display: inline-block; + width: 370px; + float: left; + position: relative; + margin-top: 1px; +} + +.ui-dialog div.autoscaler div.form-container div.form-item[rel=minInstance] { + display: block; + width: 50%; + float: left; +} + +.ui-dialog div.autoscaler div.form-container div.form-item[rel=maxInstance] { + display: inline-block; + width: 50%; + float: left; + left: -30px; + position: relative; +} + +.ui-dialog div.autoscaler div.form-container div.form-item[rel=interval] { + display: block; + width: 50%; + float: left; +} + +.ui-dialog div.autoscaler div.form-container div.form-item[rel=quietTime] { + display: inline-block; + width: 50%; + float: left; + left: -15px; + position: relative; +} + +.ui-dialog div.autoscaler div.form-container div.form-item[rel=snmpCommunity] { + display: block; + width: 50%; + float: left; +} + +.ui-dialog div.autoscaler div.form-container div.form-item[rel=snmpPort] { + display: inline-block; + width: 50%; + float: left; + left: -15px; + position: relative; +} + +.ui-dialog div.autoscaler div.form-container div.value select { + width: 88%; + float: left; +} + +div.ui-dialog div.autoscaler div.scale-up-policy-title div.form-container { + height: 55px; +} + +div.ui-dialog div.autoscaler div.scale-down-policy-title div.form-container { + height: 55px; +} + +div.ui-dialog div.autoscaler div.scale-up-policy div.multi-edit { + margin-top: 0px; +} + +div.ui-dialog div.autoscaler div.scale-down-policy div.multi-edit { + margin-top: 0px; +} + +div.ui-dialog div.autoscaler div.scale-up-policy-title { + color: #0055BB; + margin-left: -650px; + margin-top: 40px; +} + +div.ui-dialog div.autoscaler div.scale-up-policy-title label { + font-size: 13px; + margin-left: 200px; + margin-right: 10px; +} + +div.ui-dialog div.autoscaler div.scale-up-policy-title hr.policy-divider { + border-left: 1px none #38546D; + border-right: 1px none #16222C; + border-top: 1px none #38546D; + margin-bottom: 12px; +} + +div.ui-dialog div.autoscaler div.scale-down-policy-title hr.policy-divider { + border-left: 1px none #38546D; + border-right: 1px none #16222C; + border-top: 1px none #38546D; + margin-bottom: 12px; +} + +div.ui-dialog div.autoscaler div.field-group.bottom-fields hr.policy-divider { + border-left: 1px none #38546D; + border-right: 1px none #16222C; + border-top: 1px none #38546D; + margin-top: 15px; + margin-bottom: -1px; +} + +div.ui-dialog div.autoscaler div.scale-down-policy-title label { + font-size: 13px; + margin-left: 170px; + margin-right: 10px; +} + +div.ui-dialog div.autoscaler div.scale-down-policy-title { + color: #0055BB; + margin-left: -620px; + margin-top: 10px; +} + +div.ui-dialog div.autoscaler div.scale-up-policy-title div.form-container div.form-item div.value input[type=text] { + margin-left: 195px; + width: 30%; + margin-top: 1px; +} + +div.ui-dialog div.autoscaler div.scale-up-policy-title div.form-container div.form-item div.name { + margin-left: 390px; +} + +div.ui-dialog div.autoscaler div.scale-down-policy-title div.form-container div.form-item div.value input[type=text] { + margin-left: 670px; + width: 30%; + margin-top: -16px; +} + +div.ui-dialog div.autoscaler div.scale-down-policy-title div.form-container div.form-item div.name { + margin-left: 390px; +} + +div.ui-dialog div.autoscaler div.scale-up-policy div.multi-edit div.data div.data-body div.data-item { + margin-bottom: 0px; + margin-right: 22px; +} + +div.ui-dialog div.autoscaler div.scale-down-policy div.multi-edit div.data div.data-body div.data-item { + margin-bottom: 0px; + margin-right: 22px; +} + +div.ui-dialog div.autoscaler div.scale-up-policy div.slide-label { + color: #A5A3A7; + font-size: 14px; + margin-bottom: 3px; + margin-left: 755px; + width: 12px; +} + +div.ui-dialog div.autoscaler div.scale-down-policy div.slide-label { + color: #A5A3A7; + font-size: 14px; + margin-bottom: 3px; + margin-left: 755px; + width: 12px; +} + +div.ui-dialog div.autoscaler div.scale-up-policy div.hide { + background: #FFFFFF url("../images/minus.png") no-repeat 38% 59%; + border: 1px solid #D0D0D0; + border-radius: 9px 9px 9px 9px; + cursor: pointer; + float: right; + height: 15px; + margin: -20px 45px 0 11px; + width: 14px; +} + +div.ui-dialog div.autoscaler div.scale-down-policy div.hide { + background: #FFFFFF url("../images/minus.png") no-repeat 31% 54%; + border: 1px solid #D0D0D0; + border-radius: 9px 9px 9px 9px; + cursor: pointer; + float: right; + height: 15px; + margin: -20px 45px 0 11px; + width: 14px; +} + +div.ui-dialog div.autoscaler div.scale-up-policy div.expand { + background: #FFFFFF url("../images/sprites.png") repeat -541px -499px; + border: 1px solid #D0D0D0; + border-radius: 9px 9px 9px 9px; + cursor: pointer; + float: right; + height: 15px; + margin: -20px 45px 0 11px; + width: 14px; +} + +div.ui-dialog div.autoscaler div.scale-down-policy div.expand { + background: #FFFFFF url("../images/sprites.png") repeat -541px -499px; + border: 1px solid #D0D0D0; + border-radius: 9px 9px 9px 9px; + cursor: pointer; + float: right; + height: 15px; + margin: -20px 45px 0 11px; + width: 14px; +} + +div.ui-dialog div.autoscaler div.field-group div.form-container form div.form-item div.name label { + font-size: 11px; +} + +/*UI datepicker*/ +.ui-datepicker { + background: #FFFFFF 0px -2470px; + width: 300px; + height: auto; + overflow: hidden; + padding: 4px 0 0; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + /*+box-shadow:0px 3px 8px #000000;*/ + -moz-box-shadow: 0px 3px 8px #000000; + -webkit-box-shadow: 0px 3px 8px #000000; + -o-box-shadow: 0px 3px 8px #000000; + box-shadow: 0px 3px 8px #000000; + display: none; +} + +.ui-datepicker .ui-datepicker-title { + width: 100%; + margin: auto; +} + +.ui-datepicker .ui-datepicker-prev, +.ui-datepicker .ui-datepicker-next { + font-size: 13px; + color: #FFFFFF; + /*+box-shadow:0px 1px 5px #444444;*/ + -moz-box-shadow: 0px 1px 5px #444444; + -webkit-box-shadow: 0px 1px 5px #444444; + -o-box-shadow: 0px 1px 5px #444444; + box-shadow: 0px 1px 5px #444444; + /*+text-shadow:0px -1px 1px #050505;*/ + -moz-text-shadow: 0px -1px 1px #050505; + -webkit-text-shadow: 0px -1px 1px #050505; + -o-text-shadow: 0px -1px 1px #050505; + text-shadow: 0px -1px 1px #050505; + padding: 6px; + margin: 6px 13px 6px 14px; + font-size: 12px; + background: url(../images/bg-gradients.png) 0px -182px; + cursor: pointer; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; +} + +.ui-datepicker .ui-datepicker-prev:hover, +.ui-datepicker .ui-datepicker-next:hover { + /*+box-shadow:inset 0px 0px 10px #000000;*/ + -moz-box-shadow: inset 0px 0px 10px #000000; + -webkit-box-shadow: inset 0px 0px 10px #000000; + -o-box-shadow: inset 0px 0px 10px #000000; + box-shadow: inset 0px 0px 10px #000000; +} + +.ui-datepicker .ui-datepicker-prev { + float: left; +} + +.ui-datepicker .ui-datepicker-next { + float: right; +} + +.ui-datepicker .ui-datepicker-title .ui-datepicker-month { + width: 85px; + font-size: 16px; + color: #2C363F; +} + +.ui-datepicker .ui-datepicker-title .ui-datepicker-year { +} + +.ui-datepicker .ui-datepicker-title { + text-align: center; + width: 188px; + height: 19px; + padding: 3px 0 0; + /*+placement:shift 0px 6px;*/ + position: relative; + left: 0px; + top: 6px; +} + +.ui-datepicker table { + width: 277px; + height: 9px; +} + +.ui-datepicker table th, +.ui-datepicker table td { + min-width: 24px; + text-align: center; + border: 1px solid #B9B6B6; + text-indent: 0px; + padding: 7px 0; + /*[empty]+placement:;*/ +} + +.ui-datepicker table td { + cursor: pointer; +} + +.ui-datepicker table td.ui-state-disabled, +.ui-datepicker table td.ui-state-disabled:hover { + background-color: #DCDCDC; + /*+box-shadow:none;*/ + -moz-box-shadow: none; + -webkit-box-shadow: none; + -o-box-shadow: none; + box-shadow: none; + cursor: default; +} + +.ui-datepicker table td a { + text-decoration: none; + color: #485867; + font-size: 12px; +} + +.ui-datepicker table td:hover { + background-color: #6A839A; + /*+box-shadow:inset 0px 0px 4px #6B6B6B;*/ + -moz-box-shadow: inset 0px 0px 4px #6B6B6B; + -webkit-box-shadow: inset 0px 0px 4px #6B6B6B; + -o-box-shadow: inset 0px 0px 4px #6B6B6B; + box-shadow: inset 0px 0px 4px #6B6B6B; +} + +.ui-datepicker table td:hover a { + color: #FFFFFF; + text-shadow: 0px -1px #000000; +} + +/*Plugins listing*/ +.plugins-listing { +} + +.plugins-listing ul { + width: 100%; +} + +.plugins-listing ul li { + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + /*+box-shadow:0px 2px 6px #D3D3D3;*/ + -moz-box-shadow: 0px 2px 6px #D3D3D3; + -webkit-box-shadow: 0px 2px 6px #D3D3D3; + -o-box-shadow: 0px 2px 6px #D3D3D3; + box-shadow: 0px 2px 6px #D3D3D3; + border: 1px solid #A8A3A3; + background: url(../images/bg-gradients.png) 0px -29px; + width: 98%; + height: 66px; + margin: 9px auto 12px; + cursor: pointer; +} + +.plugins-listing ul li:hover { + /*+box-shadow:inset 0px 2px 4px #B9B9B9;*/ + -moz-box-shadow: inset 0px 2px 4px #B9B9B9; + -webkit-box-shadow: inset 0px 2px 4px #B9B9B9; + -o-box-shadow: inset 0px 2px 4px #B9B9B9; + box-shadow: inset 0px 2px 4px #B9B9B9; +} + +.plugins-listing ul li .title { + display: block; + float: left; + width: 90%; + font-weight: bold; + /*+text-shadow:0px 1px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px 1px #FFFFFF; + -o-text-shadow: 0px 1px 1px #FFFFFF; + text-shadow: 0px 1px 1px #FFFFFF; + color: #4A5A6A; + margin: 13px 0 7px; +} + +.plugins-listing ul li .desc { + color: #524E4E; + font-size: 13px; +} + +.plugins-listing ul li .icon { + display: block; + width: 50px; + height: 50px; + float: left; + margin: 8px 13px 13px 11px; +} + +.plugins-listing ul li .icon img { + width: 100%; + height: 100%; +} + +/*Regions*/ +.region-switcher { + display: inline-block; + position: relative; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + height: 28px; + float: left; + margin: 5px 13px 0 0; + cursor: pointer; + /*+placement:shift 27px 1px;*/ + position: relative; + left: 27px; + top: 1px; +} + +.region-selector { + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + width: 318px; + background: url(../images/bg-notifications.png) center; + height: 372px; + /*+placement:shift 185px 49px;*/ + position: relative; + left: 185px; + top: 49px; + position: absolute; + z-index: 5500; +} + +.region-selector h2 { + color: #FFFFFF; + text-align: center; + font-size: 21px; + letter-spacing: 1px; + /*+text-shadow:0px 1px 2px #000000;*/ + -moz-text-shadow: 0px 1px 2px #000000; + -webkit-text-shadow: 0px 1px 2px #000000; + -o-text-shadow: 0px 1px 2px #000000; + text-shadow: 0px 1px 2px #000000; + margin: 31px 0 14px; +} + +.region-selector .buttons { + width: 95%; + height: 33px; + margin: 5px auto 0; +} + +.region-selector .buttons .button.close { + background: url(../images/gradients.png) 0px -317px; + float: right; + margin-right: 10px; + border-bottom: 1px solid #232323; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + padding: 8px; +} + +.region-selector .buttons .button.close:hover { + /*+box-shadow:inset 0px 2px 4px #525252;*/ + -moz-box-shadow: inset 0px 2px 4px #525252; + -webkit-box-shadow: inset 0px 2px 4px #525252; + -o-box-shadow: inset 0px 2px 4px #525252; + box-shadow: inset 0px 2px 4px #525252; +} + +.region-selector .buttons .button.close span { + color: #FFFFFF; + font-weight: bold; + letter-spacing: 1px; + /*+text-shadow:0px 1px 2px #000000;*/ + -moz-text-shadow: 0px 1px 2px #000000; + -webkit-text-shadow: 0px 1px 2px #000000; + -o-text-shadow: 0px 1px 2px #000000; + text-shadow: 0px 1px 2px #000000; +} + +.region-selector .buttons .button.close:hover span { +} + +.region-selector ul { + background: #FFFFFF; + width: 94%; + /*+border-radius:4px;*/ + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + height: 237px; + border: 1px solid #B7B7B7; + overflow: auto; + margin: auto; + /*+box-shadow:inset 0px 0px 8px #A3A3A3;*/ + -moz-box-shadow: inset 0px 0px 8px #A3A3A3; + -webkit-box-shadow: inset 0px 0px 8px #A3A3A3; + -o-box-shadow: inset 0px 0px 8px #A3A3A3; + box-shadow: inset 0px 0px 8px #A3A3A3; +} + +.region-selector ul li { + background: none; + color: #415C72; + font-size: 13px; + /*+text-shadow:none;*/ + -moz-text-shadow: none; + -webkit-text-shadow: none; + -o-text-shadow: none; + text-shadow: none; + text-indent: 14px; + cursor: pointer; + border-bottom: 1px solid #CACACA; + width: 100%; + padding: 15px 0; +} + +.region-selector ul li:hover, +.region-selector ul li.active { + background: #E9E9E9 url(../images/bg-gradients.png) repeat-x 0px -31px; + /*+text-shadow:0px 1px #FFFFFF;*/ + -moz-text-shadow: 0px 1px #FFFFFF; + -webkit-text-shadow: 0px 1px #FFFFFF; + -o-text-shadow: 0px 1px #FFFFFF; + text-shadow: 0px 1px #FFFFFF; +} + +.region-switcher .icon { + display: inline-block; + float: left; + display: block; + width: 26px; + height: 26px; + background: url(../images/sprites.png) -15px -1313px; + position: absolute; +} + +.region-switcher .title { + display: inline-block; + float: right; + padding: 9px 9px 0 34px; + color: #FFFFFF; + font-size: 13px; + font-weight: 100; + max-width: 285px; + overflow: hidden; + white-space: nowrap; + /*+placement:shift -1px 0px;*/ + position: relative; + left: -1px; + top: 0px; +} + +.region-switcher:hover, +.region-switcher.active { + /*+box-shadow:inset 0px 1px 5px #000000;*/ + -moz-box-shadow: inset 0px 1px 5px #000000; + -webkit-box-shadow: inset 0px 1px 5px #000000; + -o-box-shadow: inset 0px 1px 5px #000000; + box-shadow: inset 0px 1px 5px #000000; +} + +.region-switcher:hover .icon, +.region-switcher.active .icon { + background-position: -70px -1311px; +} + +/*Action icons*/ +.action.edit .icon { + background-position: 1px -1px; +} + +.action.edit:hover .icon { + background-position: 1px -583px; +} + +.start .icon, +.startByAdmin .icon { + background-position: -169px -1px; +} + +.start:hover .icon, +.startByAdmin:hover .icon { + background-position: -169px -583px; +} + +.stop .icon, +.removeVmwareDc .icon, +.release .icon { + background-position: 0px -31px; +} + +.stop:hover .icon, +.removeVmwareDc:hover .icon, +.release:hover .icon { + background-position: 0px -613px; +} + +.restart .icon, +.releaseDedicatedZone .icon { + background-position: 0px -63px; +} + +.restart:hover .icon, +.releaseDedicatedZone:hover .icon { + background-position: 0px -645px; +} + +.destroy .icon, +.expunge .icon, +.remove .icon, +.removeMulti .icon, +.delete .icon, +.decline .icon, +.deleteacllist .icon { + background-position: 1px -92px; +} + +.destroy:hover .icon, +.expunge:hover .icon, +.remove:hover .icon, +.delete:hover .icon, +.deleteacllist:hover .icon { + background-position: 1px -674px; +} + +.migrate .icon, +.migrateToAnotherStorage .icon { + background-position: 0px -125px; +} + +.migrate:hover .icon, +.migrateToAnotherStorage:hover .icon { + background-position: 0px -707px; +} + +.migrate .icon, +.migrateVolume .icon { + background-position: 0px -125px; +} + +.migrate:hover .icon, +.migrateVolume:hover .icon { + background-position: 0px -707px; +} + +.viewMetrics .icon { + background-position: -40px -32px; +} + +.viewMetrics:hover .icon { + background-position: -40px -32px; +} + +.refreshMetrics .icon { + background-position: 0px -62px; +} + +.refreshMetrics:hover .icon { + background-position: 0px -62px; +} + +.attach .icon, +.attachISO .icon, +.attachDisk .icon, +.associateProfileToBlade .icon { + background-position: -104px -3px; +} + +.attach:hover .icon, +.attachISO:hover .icon, +.attachDisk:hover .icon { + background-position: -101px -585px; +} + +.detach .icon, +.detachISO .icon, +.detachDisk .icon, +.disassociateProfileFromBlade .icon { + background-position: -101px -65px; +} + +.detach:hover .icon, +.detachISO:hover .icon, +.detachDisk:hover .icon, +.disassociateProfileFromBlade:hover .icon { + background-position: -101px -647px; +} + +.resetPassword .icon, +.changePassword .icon { + background-position: -68px -30px; +} + +.resetPassword:hover .icon, +.changePassword:hover .icon { + background-position: -68px -612px; +} + +.resetSSHKeyForVirtualMachine .icon { + background-position: -196px -3px; +} + +.resetSSHKeyForVirtualMachine:hover .icon { + background-position: -195px -586px; +} + +.changeService .icon { + background-position: -38px -33px; +} + +.changeService:hover .icon { + background-position: -38px -615px; +} + +.snapshot .icon, +.takeSnapshot .icon { + background-position: -36px -91px; +} + +.snapshot:hover .icon, +.takeSnapshot:hover .icon { + background-position: -36px -673px; +} + +.recurringSnapshot .icon { + background-position: -69px -95px; +} + +.recurringSnapshot:hover .icon { + background-position: -69px -677px; +} + +.downloadVolume .icon, +.downloadTemplate .icon, +.downloadISO .icon { + background-position: -35px -125px; +} + +.downloadVolume:hover .icon, +.downloadTemplate:hover .icon, +.downloadISO:hover .icon { + background-position: -35px -707px; +} + +.createVolume .icon { + background-position: -70px -124px; +} + +.createVolume:hover .icon { + background-position: -70px -706px; +} + +.enable .icon, +.enableStaticNAT .icon { + background-position: -102px -92px; +} + +.enable:hover .icon, +.enableStaticNAT:hover .icon { + background-position: -102px -676px; +} + +.disable .icon, +.disableStaticNAT .icon { + background-position: -136px -93px; +} + +.disable:hover .icon, +.disableStaticNAT:hover .icon { + background-position: -136px -677px; +} + +.add .icon, +.addNew .icon, +.addLdapAccount .icon, +.assignVm .icon, +.rootAdminAddGuestNetwork .icon { + background-position: -37px -61px; +} + +.add:hover .icon, +.addNew:hover .icon, +.addLdapAccount:hover .icon, +.assignVm:hover .icon, +.rootAdminAddGuestNetwork:hover .icon { + background-position: -37px -643px; +} + +.assignVmToAnotherAccount .icon { + background-position: -232px -97px; +} + +.assignVmToAnotherAccount:hover .icon { + background-position: -231px -678px; +} + +.create .icon, +.createTemplate .icon, +.enableSwift .icon, +.addVM .icon, +.dedicateZone .icon, +.dedicate .icon { + background-position: -69px -63px; +} + +.create:hover .icon, +.createTemplate:hover .icon, +.enableSwift:hover .icon, +.addVM:hover .icon, +.dedicateZone:hover .icon { + background-position: -69px -645px; +} + +.copyTemplate .icon, +.copyISO .icon { + background-position: -138px -2px; +} + +.copyTemplate:hover .icon, +.copyISO:hover .icon { + background-position: -138px -584px; +} + +.createVM .icon { + background-position: -137px -32px; +} + +.createVM:hover .icon { + background-position: -137px -614px; +} + +.enableMaintenanceMode .icon { + background-position: -138px -65px; +} + +.enableMaintenanceMode:hover .icon { + background-position: -138px -647px; +} + +.cancelMaintenanceMode .icon { + background-position: -138px -123px; +} + +.cancelMaintenanceMode:hover .icon { + background-position: -138px -705px; +} + +.lock .icon { + background-position: -104px -124px; +} + +.lock:hover .icon { + background-position: -104px -706px; +} + +.updateResourceLimits .icon { + background-position: -100px -32px; +} + +.updateResourceLimits:hover .icon { + background-position: -100px -614px; +} + +.addVlanRange .icon, +.addVmwareDc .icon { + background-position: -37px -62px; +} + +.addVlanRange:hover .icon, +.addVmwareDc:hover .icon { + background-position: -37px -62px; +} + +.removeVlanRange .icon { + background-position: 1px -92px; +} + +.removeVlanRange:hover .icon { + background-position: 1px -92px; +} + +.resize .icon, +.updateResourceCount .icon { + background-position: -167px -66px; +} + +.resize:hover .icon, +.updateResourceCount:hover .icon { + background-position: -167px -648px; +} + +.generateKeys .icon, +.networkACL .icon { + background-position: -167px -95px; +} + +.generateKeys:hover .icon, +.networkACL:hover .icon { + background-position: -167px -677px; +} + +.revertSnapshot .icon, +.restoreVM .icon, +.restore .icon, +.recover .icon { + background-position: -168px -31px; +} + +.reset .icon, +.reinstall .icon { + background-position: -168px -31px; +} + +.scaleUp .icon { + background-position: -167px -66px; +} + +.revertSnapshot:hover .icon, +.restoreVM:hover .icon, +.restore:hover .icon { + background-position: -168px -613px; +} + +.reset:hover .icon { + background-position: -168px -613px; +} + +.enableVPN .icon { + background-position: -198px -3px; +} + +.enableVPN:hover .icon { + background-position: -197px -586px; +} + +.disableVPN .icon { + background-position: -198px -32px; +} + +.disableVPN:hover .icon { + background-position: -197px -615px; +} + +.addIpRange .icon { + background-position: -197px -65px; +} + +.addIpRange:hover .icon { + background-position: -197px -647px; +} + +.forceReconnect .icon { + background-position: -196px -95px; +} + +.forceReconnect:hover .icon { + background-position: -196px -677px; +} + +.manage .icon { + background-position: -165px -122px; +} + +.manage:hover .icon { + background-position: -165px -704px; +} + +.unmanage .icon { + background-position: -196px -122px; +} + +.unmanage:hover .icon { + background-position: -196px -704px; +} + +.configureSamlAuthorization .icon { + background-position: -165px -122px; +} + +.configureSamlAuthorization:hover .icon { + background-position: -165px -704px; +} + +.viewConsole .icon { + background-position: -231px -2px; +} + +.viewConsole:hover .icon { + background-position: -229px -586px; +} + +.moveTop .icon { + background-position: -24px -161px; +} + +.moveTop:hover .icon { + background-position: -24px -734px; +} + +.moveBottom .icon { + background-position: -98px -161px; +} + +.moveBottom:hover .icon { + background-position: -98px -734px; +} + +.moveUp .icon { + background-position: -2px -161px; +} + +.moveUp:hover .icon { + background-position: -2px -734px; +} + +.moveDown .icon { + background-position: -55px -161px; +} + +.moveDown:hover .icon { + background-position: -55px -734px; +} + +.moveDrag .icon { + cursor: move; + /*+border-radius:10px;*/ + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + -khtml-border-radius: 10px; + border-radius: 10px; + border-radius: 10px 10px 10px 10px; + background-position: -74px -162px; +} + +.moveDrag:hover .icon { + background-color: #FFFFFF; + cursor: move !important; +} + +.uploadVolume .icon { + background-position: -232px -34px; +} + +.uploadVolume:hover .icon { + background-position: -230px -615px; +} + +.editTags .icon { + background-position: -228px -65px; +} + +.editTags:hover .icon { + background-position: -228px -646px; +} + +.replaceacllist .icon, +.replaceACL .icon, +.updateIpaddr .icon, +.changeAffinity .icon { + background-position: -264px -2px; +} + +.replaceacllist:hover .icon, +.replaceACL:hover .icon, +.updateIpaddr:hover .icon, +.changeAffinity:hover .icon { + background-position: -263px -583px; +} + +.releaseFromAccount .icon { + background-position: -230px -123px; +} + +.releaseFromAccount:hover .icon { + background-position: -229px -704px; +} + +.addAccount .icon { + background-position: -231px -96px; +} + +.addAccount:hover .icon { + background-position: -230px -677px; +} + +.linktoldap .icon { + background-position: -197px -65px; +} + +.linktoldap:hover .icon { + background-position: -197px -647px; +} + +.label-hovered { + cursor: pointer; + color: #0000FF !important; +} + +.accounts-wizard table { + margin: 0; + width: 100%; + table-layout: fixed; +} + +.accounts-wizard .ui-button { + display: inline-block !important; + float: none !important; +} + +.accounts-wizard td:last-child { + border: none; +} + +.accounts-wizard tbody tr:nth-child(even) { + background: #DFE1E3; +} + +.accounts-wizard tbody tr:nth-child(odd) { + background: #F2F0F0; +} + +.accounts-wizard .content { + display: inline-block; +} + +.accounts-wizard .content td { + white-space: nowrap; + text-overflow: ellipsis; +} + +.accounts-wizard .content td.select, +.accounts-wizard .content th.select { + background: none; + border-right: 1px solid #BFBFBF; + width: 60px !important; + min-width: 60px !important; + max-width: 60px !important; +} + +.accounts-wizard .content .select input { + padding: 0; + width: auto; + height: auto; + margin: 18px 0 0 24px; +} + +.accounts-wizard .content:last-child { + margin-left: 14px; +} + +.accounts-wizard table thead th:first-child { + width: 50px; + min-width: 50px; + max-width: 50px; +} + +.accounts-wizard .input-area { + width: 320px; + font-size: 13px; + color: #485867; + text-shadow: 0px 2px 1px #FFFFFF; +} + +.ldap-account-choice { + border: none !important; + border-radius: 0 0 0 0 !important; +} + +.manual-account-details .name { + margin-top: 2px; + width: 100px; + float: left; + padding-bottom: 10px; +} + +.manual-account-details { + height: auto !important; + overflow: visible !important; + overflow-x: visible !important; +} + +.manual-account-details label.error { + display: block; + font-size: 10px; +} + +.manual-account-details .value { + float: left; +} + +.manual-account-details .form-item:after { + content: "."; + display: block; + clear: both; + visibility: hidden; + line-height: 0; + height: 0; +} + +.manual-account-details .form-item { + padding: 5px; + width: 100%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.manual-account-details select, +.manual-account-details input { + width: 150px; +} + +.manual-account-details input { + background: #F6F6F6; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + border-radius: 4px 4px 4px 4px; + border: 1px solid #AFAFAF; +} + +.manual-account-details > *:nth-child(even) { + background: #DFE1E3; +} + +.manual-account-details > *:nth-child(odd) { + background: #F2F0F0; +} + +.manual-account-details .value { + display: inline-block; +} + +/*GPU*/ +div.gpugroups div.list-view div.fixed-header { + position: relative; + left: 12px !important; + top: 0px !important; +} + +div.gpugroups div.list-view div.fixed-header table { + width: auto; +} + +div.gpugroups div.list-view div.data-table table { + margin-top: 0; +} + +div.gpugroups div.list-view { + position: relative; + height: auto !important; + margin-top: 0 !important; + border: none !important; +} + +.gpugroups { + float: left; + height: 100%; + width: 100%; + overflow-x: hidden; + overflow-y: auto; +} + +.gpugroups .gpugroup-container { + border: 1px solid #C8C2C2; + border-radius: 3px; + height: auto !important; + margin: 12px; + padding: 0; + position: relative; + float: left; + width: auto; +} + +.gpugroups .gpugroup-container .title { + font-size: 13px; + font-weight: 100; + padding: 12px 12px 5px; +} + +.ui-dialog .ui-button.add { + background: transparent linear-gradient(to bottom, #f7f7f7 1%, #eaeaea 100%) repeat 0px 0px; + font-size: 12px; + height: 12px; + width: auto; + margin: 0px 0px 12px; + padding: 5px 7px 5px 6px; +} + +.ui-dialog .ui-button.add:hover { + background: #E5E5E5 repeat 0px 0px; + box-shadow: inset 0px 0px 5px #C3C3C3; +} + +.ui-dialog .ui-button.add span { + background: transparent url("../images/icons.png") no-repeat -626px -209px; + padding: 0 0 3px 18px; +} diff --git a/cosmic-client/cosmic-ui/css/cloudstack3.hu.css b/cosmic-client/cosmic-ui/css/cloudstack3.hu.css new file mode 100644 index 0000000000..77259e14d5 --- /dev/null +++ b/cosmic-client/cosmic-ui/css/cloudstack3.hu.css @@ -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. +*/ + +/* + * correct the breadcrumbs, hungarian translation is longer + * than the english default. + */ +#breadcrumbs div.home { + width: 100px; +} diff --git a/cosmic-client/cosmic-ui/css/cloudstack3.ja_JP.css b/cosmic-client/cosmic-ui/css/cloudstack3.ja_JP.css new file mode 100644 index 0000000000..c4164d9297 --- /dev/null +++ b/cosmic-client/cosmic-ui/css/cloudstack3.ja_JP.css @@ -0,0 +1,83 @@ +/*[fmt]1C20-1C0D-E*/ +/* +* 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. +*/ +#header div.view-switcher { + font-size: 9px; +} + +.dashboard.admin .dashboard-container .stats ul li .name { + font-size: 10px; +} + +.dashboard.admin .dashboard-container .stats ul li .percentage { + float: left; + font-size: 15px; + font-weight: bold; + margin: 13px 0 0; + /*+text-shadow:0px -2px 1px #FFFFFF;*/ + -moz-text-shadow: 0px -2px 1px #FFFFFF; + -webkit-text-shadow: 0px -2px 1px #FFFFFF; + -o-text-shadow: 0px -2px 1px #FFFFFF; + text-shadow: 0px -2px 1px #FFFFFF; +} + +.dashboard.admin .dashboard-container .stats ul li .value .content { + font-size: 10px; +} + +div.toolbar div.filters label { + font-size: 9px; +} + +div.toolbar div.filters select { + width: 82px; + font-size: 11px; +} + +div.toolbar div.button.add, +div.toolbar div.button.refresh, +div.toolbar div.button.add, +div.toolbar div.button.main-action, +.toolbar div.button.header-action, +.detail-group .button.add { + font-size: 10px; + white-space: nowrap; +} + +table tbody td.quick-view, +table thead th.quick-view { + font-size: 8px; +} + +.multi-wizard.instance-wizard .progress ul li span.multiline { + font-size: 9px; +} + +.multi-wizard .review .select-container .select .name { + white-space: nowrap; + font-size: 10px; +} + +.quick-view-tooltip .detail-view .detail-group.actions .action.text .label { + font-size: 8px; +} + +.detail-view .multi-edit table tr th, .detail-view .multi-edit table tr td { + font-size: 8px; +} \ No newline at end of file diff --git a/cosmic-client/cosmic-ui/css/custom.css b/cosmic-client/cosmic-ui/css/custom.css new file mode 100644 index 0000000000..021362338d --- /dev/null +++ b/cosmic-client/cosmic-ui/css/custom.css @@ -0,0 +1,21 @@ +/* +* 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 custom.css to override the default CloudStack styles +*/ diff --git a/cosmic-client/cosmic-ui/css/token-input-facebook.css b/cosmic-client/cosmic-ui/css/token-input-facebook.css new file mode 100644 index 0000000000..bb29276ed8 --- /dev/null +++ b/cosmic-client/cosmic-ui/css/token-input-facebook.css @@ -0,0 +1,133 @@ +/* + * jQuery Plugin: Tokenizing Autocomplete Text Entry + * Version 1.6.0 + * + * Copyright (c) 2009 James Smith (http://loopj.com) + * Licensed jointly under the GPL and MIT licenses, + * choose which one suits your project best! + * + */ + +/* Example tokeninput style #2: Facebook style */ +ul.token-input-list-facebook { + overflow: hidden; + height: auto !important; + height: 1%; + width: 233px; + border: 1px solid #AFAFAF; + cursor: text; + font-size: 12px; + font-family: Verdana; + min-height: 1px; + z-index: 2147483647; + margin: 0; + padding: 0; + background-color: #F6F6F6; + list-style-type: none; + clear: left; +} + +ul.token-input-list-facebook li input { + border: 0; + width: 100px; + padding: 3px 8px; + background-color: white; + margin: 2px 0; + -webkit-appearance: caret; +} + +li.token-input-token-facebook { + overflow: hidden; + height: auto !important; + height: 15px; + margin: 3px; + padding: 1px 3px; + background-color: #eff2f7; + color: #000; + cursor: default; + border: 1px solid #ccd5e4; + font-size: 11px; + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + float: left; + white-space: nowrap; +} + +li.token-input-token-facebook p { + display: inline; + padding: 0; + margin: 0; +} + +li.token-input-token-facebook span { + color: #a6b3cf; + margin-left: 5px; + font-weight: bold; + cursor: pointer; +} + +li.token-input-selected-token-facebook { + background-color: #5670a6; + border: 1px solid #3b5998; + color: #fff; +} + +li.token-input-input-token-facebook { + float: left; + margin: 0; + padding: 0; + list-style-type: none; +} + +div.token-input-dropdown-facebook { + position: absolute; + width: 233px; + background-color: #fff; + overflow: hidden; + border-left: 1px solid #ccc; + border-right: 1px solid #ccc; + border-bottom: 1px solid #ccc; + cursor: default; + font-size: 11px; + font-family: Verdana; + z-index: 2147483647; +} + +div.token-input-dropdown-facebook p { + margin: 0; + width : 233px; + padding: 5px; + font-weight: bold; + color: #777; +} + +div.token-input-dropdown-facebook ul { + margin: 0; + padding: 0; +} + +div.token-input-dropdown-facebook ul li { + background-color: #fff; + padding: 3px; + margin: 0; + list-style-type: none; +} + +div.token-input-dropdown-facebook ul li.token-input-dropdown-item-facebook { + background-color: #fff; +} + +div.token-input-dropdown-facebook ul li.token-input-dropdown-item2-facebook { + background-color: #fff; +} + +div.token-input-dropdown-facebook ul li em { + font-weight: bold; + font-style: normal; +} + +div.token-input-dropdown-facebook ul li.token-input-selected-dropdown-item-facebook { + background-color: #3b5998; + color: #fff; +} diff --git a/cosmic-client/cosmic-ui/dictionary.jsp b/cosmic-client/cosmic-ui/dictionary.jsp new file mode 100644 index 0000000000..2876a35e40 --- /dev/null +++ b/cosmic-client/cosmic-ui/dictionary.jsp @@ -0,0 +1,1108 @@ +<%-- +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. +--%> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> + + + + + +<%-- +**** +NOTE +**** + +Please use dictionary2.jsp for all new mappings. This is due to +file size constraints for JSP files. + +If you add anything else to this file, an error might occur at runtime! +--%> + +<% long now = System.currentTimeMillis(); %> + diff --git a/cosmic-client/cosmic-ui/dictionary2.jsp b/cosmic-client/cosmic-ui/dictionary2.jsp new file mode 100644 index 0000000000..0867e6cff3 --- /dev/null +++ b/cosmic-client/cosmic-ui/dictionary2.jsp @@ -0,0 +1,1097 @@ +<%-- +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. +--%> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> + + + + +<% long now = System.currentTimeMillis(); %> + diff --git a/cosmic-client/cosmic-ui/error.jsp b/cosmic-client/cosmic-ui/error.jsp new file mode 100644 index 0000000000..e5b6f8941d --- /dev/null +++ b/cosmic-client/cosmic-ui/error.jsp @@ -0,0 +1,30 @@ +<%-- + 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. +--%> + + + + + + Apache CloudStack + + + +

Oops, looks like we hit an error. Ask your Administrator to look into it.

+ + diff --git a/cosmic-client/cosmic-ui/images/ajax-loader-small.gif b/cosmic-client/cosmic-ui/images/ajax-loader-small.gif new file mode 100644 index 0000000000000000000000000000000000000000..6a79f6004f6e9dbe7bff48624f2ca2371f1ad24d GIT binary patch literal 10781 zcmeI2c~}!?8o(10?kmBxwOzYA1Vkt#nVHN?W&&vDNC*mufECeh2@p*Ll9)sZ9$i4t z6pL5uQEQdD)$QfEo^5Rfv0HZSLDklyZh_L>+FjS$YParM+6f`keV&v&%OCtj@=p%F z-}`+-zVG+E?>ouK)!~Fe#1rwlCh!g)K78!hv6ClHo;r1^qod>O*|Xi<-RI7o>*?va zc=6)p%a^ZRyVlp&H!v`8{rdGAH*Vazb?eTZJ9qEi9UL6w{U49SAEOJjCB-yZsDvji8EWuuGD)8wKguS#UUTM z9C@X+R^c+(6p$SBT#|2KetzJgwYA)6hqK7|^Ll?Vu%o2jW>gdz9o8lGN+UZw$upHr zP1}tIr`29!wbuCli-jhu)9Nr;Z2(=24vossSLa$BPJ^Y=n9F2KSx#;?SE+GAh3a6O zhEWEEAT31D6i8A8PB9FQV-(|K8EfT|TBF73W2=5$xkGvLg#CXGO)yQk1Mr0N2VicaQ^MAfx-$(48@x0_VEBl7q?nVo{$L;K9=KnbO z!@d9h=kE9a{_f80Z@;qy8h+g|Mtbe=ly-XpZ)dPr&q6BzVyk*7cZRu zsOQ5E-hc1hU;f4jSs;RNzcTcQbv3yy* zt8VEMr^CM3X0_DTR4+1{7A`PWRaO|vAOG!R^XHY#EuHh|?2_W5S%otT^7Cd)&(-JX zv)8Hsm=kvyJ+m*Fvv$6UP78esCes@wt*6(MNU6ccF_@M+_| zuKptf$IE30d$rAUyG%-5YHH~OChFaLkMleG^f5bErAwqulGFWVjd@V`8A(R<)%!Z1 z?=;0O+%sv{@{*7|$yALV)g~z)r}%9dXLl7{1fBgPA$q+u2skqs(oERQffCjAa{lHp%<`dG0*>mR3qavlbOkM#oYpRAW#B!39=yr7$EQ&~%DY8>u4dzVa zq>Lk#IviKwT=_$RDh8(^3Z_YlMo=hV&g5me63~wwwWNmDLij}E7cZcshFGoy^kajP z8VgCa`f{e2&IV5z$4aV6=8=@TX~TwSR#Kmy-cSxoT6O@`#$-#Vt}AjbBZ!D<76Hl9 z)mIZYm5aNGrf;`65Vycpzb^XHElE5oSQw5~M^@D*M3lye;^IUxVFc?c{*1`j*vL?{ zc&bJvpfZMxv&LD<3(7A3f2-TE~_F58m1{FrenKMm=Xa43d_rKS(P6< zT48zPaaomLyg#|DKp7Q$NR6PCBgi|Npajuvo;|6FG$kX7$!|e4i#5Q!kZ?A(x0bn$6e9SnE8{AB`bAL_UO@yN4j?(1fzuf zj>3*TOkB8LcrP{wFhzAWxRJM9i<1rD`5T7%*ySr1niFbjCND0VKOw$8e!{$HnRr&h z(l~KKOsO^}Ja@gdhDl=~lb8Xj31JZWK*f-=rxljhnphor(~#@Xg2Ht?FsV&$isWFt0Zy zRjg(DA3L!A=J$u*8ti{BvO>71m@k|kHFsV-zpX7~1}rOxm&6N;g`4Lj)0)X?g)@^> zV5TT8Y8sg=j(sH(PRlrQG^nfbl3<}a5`}T@mKg=BARWlC=@ks(5JLvm)ey_wHuPgh ztt(GFmCmO>zjy(4HMAD)wxJ&z)YVw%D#ue5ns=I7sV5D<2hD1#JUk?6>nzzljXSqd zFc=d4^b4jK(KGmh8*``Y*M}9)Y+b%BrLC-_{nF-?^@7H7)3(`PpI9LY3I5PveEiS= zLopD?xJEUhq6iWNF%nZ@I70<)ntNHUQSHZ$);8XFTu;j{ULf0eS=?tTKQ?Hav9L|4 z*EXr4vfSNS=Ly*=QAB5Z*T98dP47C%%b}}8MW3&xlb>o5M)H}w1)4LGZSEgzSHuF` nCO(m@k9}yxZ3(gTblLXQOChhDztK;MiHOZgYi?n?Ntr(Zst&+^ literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/images/ajax-loader.gif b/cosmic-client/cosmic-ui/images/ajax-loader.gif new file mode 100644 index 0000000000000000000000000000000000000000..a314f12ec2dc895415c16c7f7b5be5018e55cfc0 GIT binary patch literal 12018 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 lB@w`xRCf5}sgBOm1UF#LzW~%+;6DHW literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/images/bg-breadcrumb-project-view.png b/cosmic-client/cosmic-ui/images/bg-breadcrumb-project-view.png new file mode 100644 index 0000000000000000000000000000000000000000..9325d8815a41d328eba89f28e6a1fa962398cf40 GIT binary patch literal 2860 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 literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/images/bg-breadcrumbs-project-view.png b/cosmic-client/cosmic-ui/images/bg-breadcrumbs-project-view.png new file mode 100644 index 0000000000000000000000000000000000000000..734acd802fd27e4e7f575742e03b62a6aa1bed51 GIT binary patch literal 2857 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 bw(ai#G2Hc_8^cDi00000NkvXXu0mjf1TuNB literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/images/bg-dialog-header.png b/cosmic-client/cosmic-ui/images/bg-dialog-header.png new file mode 100644 index 0000000000000000000000000000000000000000..cf547443e38479167751b0e4dc433aecb42d0fe9 GIT binary patch literal 1059 zcmbVLJ8#oa6m~&KsHN%xqA*#G*hqY@^KfhuN@6>0plPEt&@4ze_HAON_BHk`aXXbt z4BZf2Ktf0?2r(d7VL(EaP{lvs2OyXjK~#;o&Z96?9kAs4Jm2@7bM86Y)rHx~@r&aO z!%UXv)Eb?~=zr<-2|Av+ezr%a3q)&>CA>nqx`&v8iJOQm+jc&RLkW6moDw_*rN?;?Rvw^+p_;Qfv`n)n-N5?$J_Dx79&kdYvRI03kJ;bIHAZ~yR zWHYG*OaUn+U1kLU0S^Tph;azyv?v3}4nK~ndFF~-Q%gfFy32EILR^{WyWMW0n@nJ@ z#Y0Jwcp&hC5T_P#f88N^5O@68h(SfZ;aM)Ru){`-dK0gaJV!kpgkZas$|14i4--XG z#s|8~!vx@MJBq7s?UNe%>&B7Retq3Vd=2?{%`@nGti(oOn!DqLB17tpyy#igjr1kbS$Km0#Gs~HJeI707e`~VMQsaWKvo|0ci-K z28yMOph5_;Nhl?ytT@7!9iQlqfkwI()g5A`W3jU0A)R2aj`8ZK0;+9Hu;0cmt1PA2 zWy>*f*N+}&5UYwj>mD*o9=6$jc4g}b^(;&!OH^AD6)}ps2qdj279=g16(p@dJs)7r z|H&9no#CU>_@`7xN3{N;-tcG=9s-v0TIvoFG){(GXO{0iT^eDLJd^ZQ>9!l~Wx@$~d` l6~tn(omY=?JKJyJwaqc6b8u@h^s7^#{XSRSN(B literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/images/bg-gradient-white-transparent.png b/cosmic-client/cosmic-ui/images/bg-gradient-white-transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..9666012576113313216d8446cdbd50e3b716daae GIT binary patch literal 4052 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 literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/images/bg-gradients.png b/cosmic-client/cosmic-ui/images/bg-gradients.png new file mode 100644 index 0000000000000000000000000000000000000000..52016f0cf487158c238037ef50e51ae64d124d9a GIT binary patch literal 7539 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 literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/images/bg-header.png b/cosmic-client/cosmic-ui/images/bg-header.png new file mode 100644 index 0000000000000000000000000000000000000000..bcef36ef800af255592e2f7effc8efffe24bd419 GIT binary patch literal 2824 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 literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/images/bg-login.jpg b/cosmic-client/cosmic-ui/images/bg-login.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7a21002a2071cfed2849450cd9083e0013967dd5 GIT binary patch literal 38127 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 U{*b^Q68J*`|1SxA^Lgff0cv4?(*OVf literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/images/bg-login.png b/cosmic-client/cosmic-ui/images/bg-login.png new file mode 100644 index 0000000000000000000000000000000000000000..f65b22a06eac3348e4839b5b6b00a76aaa06fb07 GIT binary patch literal 8401 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 literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/images/bg-naas.png b/cosmic-client/cosmic-ui/images/bg-naas.png new file mode 100644 index 0000000000000000000000000000000000000000..851709906332684c45f16fd1f6effbeba39dc013 GIT binary patch literal 10706 zcmeHscT`i~_Gb_k1pyl(BA^lksVYr6f&!uUDxhMBi1ZR5Q~^;?kRTw0A|ky>m)@jH z7XpMNA|9XI6zr+uLc9RlkQqpULX+5xjz>JC^hXO2*hmQq^@pg_|(nY z&FiU~`xS_~`W1IiH%F)EP!Py(I^6_jYO;JmiGtfw(|Hy8UdPRtT1>$hf&*^`}dD>_k4WC7#kb)BM#>vEkXTqWh?`h-~MHM#|#A zVKrKrP`sI5ImFa@;#88Bq>*&!sRA|LGe5(-zje*di>rn)gWW-=PgFj+>UF@+0NM>! zR1~?^%G3;E@Ec<}0czDlwcflOMQ2}8OL@Q$8o|)&9e+pXMCf^tYCx=7E=W~_AvFEN zbraCXqo9ZV4v*(RhGL+Hf+34zpwRT)6a)jv=KWO;hL7=}E9aoE?tz{vfQpCAqwj+( zZ-UM{>U@&}O^ShTLM$A$KwoM>t%IjoDnKVrfo>YTio5|jiU2+A;pg`QMWlevYiw93 z{uU`cO%Mhwl~SQt2bQ}ZW^+Q!gUP}|oL6WN!YOiI=FyQysjA}b{wZ9y!=%`MZhi-W zKE`tZ+HM~B4YHLE4$8#Vu-S{Pv>c~jd;ECraABy(T@?hH^$s366qTsF6smAM)aCG# z0O{ycJC=_=lo-bfcGYUo$K`2@P4_>xxu5l}Zfa^_etuH>o0`p|K8v73Si60z#liEv zU_}~b`)BhnaM*3zFwLX8Kij`=>E>J>LY|CxJhK?5Nvma{(|E?NeS2 zA3`S>L!XAv4#DYUl>^O>0-#Vw2+{)tvbzTsH|?ub={^Ah-TN3O{N*n9R@+6<*5jAk zj?J{O&>qS}sDZz=tDRP3eiC}w^Wlx+7ivN;2fpwKKa}{P$|Kck^z>zdJDYgB*;h6t zclP~9CoguMNiqM${HAzUhg$g^(US=d{pHt+PZf1t`eI(p z*zkJWVW=E_^laGu){``1tm%C`&C-{)w}3=Oj)eUa5bR1)qJbep|5a-4RInXVcNMqZ*^G#{A2o1t=-4 zOZ*AF(@&f$nB?AzUJ>buoKK(cn3tUAp1-pjyI;YCxKJ0FF2cCr7t22A=5me2aCMvBl)bh3fsn$U z;$~(kTq7LF5#Pt0sGWE{ky}{8w4gAz5Ld`zwrSRH8dqRv5>%>TCTTKPaPmh|ky&B7 z;i?I@39LXNKdKOx?`EpM2X-3|lw&qg21?dKZ1uCO?8Rr-lub!IJk=@TaX z?RxpZRG_bL!-oT&h8U&7f-E_8No`4sO5@GqZo_$2Hz+u)lHYdN2o`bLn#F z%D#U5`qk??dMkRQRAarm+#9(Hx(d481K$Te4NRm+qm*u;Zxc`i6dHxEeQ51xEn1Ub z^A!rIn77WgBGojyMq0~R6DF(<6Hv$TVh(3B8VAN2M>iHS98imiypnkU$+%JaOuxjZg)FpZ=V z)VYPz5 zLJy#ifbv2G7+{RA!|sOJ9HH(81YT{4RguR)cPgfcci()hyUTGm?nTLqg|?EbPkYwh zm0YC|J`BbWI1}{wvGd)^-6w?uWN-#1i(&D8`totj=OCz_ErQstp>R$MJ`}dOW z;ZU$!26AO$+S0Q%bL9j(5PTc%q=p6gte6BjO(aedy5rx7=!ex?6n znL}`Xkm#{>_s<$^--^t;P#}I*R6a>n_m%#g_v?303+G>WBd~D}E)dJ?-_FwxyCyLr zGxBOu{edvA{)U#YUI20}zERpA+$otOx=U+iIwvv3)t#D-g zI}f$Ot9l7Fuoc*%qi=sPG_vBb(>116f;W+=*SYdt@i8kg<{Gu!B~xo5v@lZz3~J%n zGVr=jE~zL(WPtd+vJdl4HjSJ~C!TFTJBzHzd8uO|#$&nIRQyb?Leaw?!#u}vOys5c z@nXfV(KW&@;|4`Ui{#Sart1NE&S7?KeJ|Ro8Jt&H%$OGO!rM?NTb;5N9bMG5fV3| zaYt%x9Anc+IZ*Cx#+Wgf>5V&%L#YsIB}Jw5b>Z@E64k}FMBl}O*v&Y~c)-NZR)enl zG0ropWNI8YA+oB;*3H*^m`;X^vC6QTy-n--#qWO4e}0*A5>Me}m7n%~gD9S58sqcj zf$>N@Q%l`Q)j*k~Qf(gF)Ej(P9+_Cd1<*7xeC%mbidO#Go2I=^)5|Z$5?~ucZ_*Rf zgM!8PW*%Zb)gTBCgZO9cZshvY-m|0k=`jR_Muou#8(+OQDliM@EWTQ#mdV?@taz^| z9wv+#bT_0mq?Xn_aIPZ-G*u%UDa+g$vV+*ICjZ|T=o8}^a!S~y%>Xw_CuJLd0UPWo zcre)CFa;MRl{6YO+Ha{6?@p;>qsCQi4reCGGa=7f;U70gW;7|sBg2OdqeYu)(M_Eqpmx|}XGA&Pi=*iIa49>$(hyA6T7bJpr$5+vVBOgYxk~fkQT3iBi z4pv%MxymqI?OnXhy?&Cn?6%CQ#5VX0oQIr^XE&G+yb>Zrf4Wz`Z8DrkP2fsMpdV%Y zut{@}6vF`Z<`oAmV?7WkKmY^^4F`et=)ip$1oF8F0hEkUb+8kzcjJ%t9PDIqrIhwMbn#;luEVa9Cd1 zA^N4_N)Ksm&SQ`l)}kQ!y%Cm12yB+Dv@sJpe*~0R7iyo00YH>Kb9gZHHkBBE7XpT~~sTbHekR1^U@Cy`jFrJ8#u&XT3Bc zhTCvG=?U{~;P8GK+3S10NGh{WBtf@e4CgzR@BleqAJk59&nk6~KY7*rYxY3FO3FMQ z`9{KZI&r=XIb)qKvUWR5+)IH?yI-78)Gvd#%NL=QFJJNYrZDxC3G6-ojmZVma||t> z-b@ikAaIta?1~I7tT0njPN`|agACK&Kk=B#RT28ZLAsS99?}rZ-}|=HO%^OyU}!Pv zaNAo(`R1U)?0!Xu*S*rmfw#mmD|M`useVoIzzP=VB%bEZJHqO}tH)9OGPW+lCwQ>> zzp()&7yMw*&OHxnJ1;#X#LTO(@a@s8?1mI`-$|5#u$vkBeND>^Xkh`sL$(?kWx=I+ z|Cy-5nk*I|C^Yr6Q5zpXp9w2GM+>Mif&M;1hZ+7l{^~=3a)ReS8GmQ<4}QSIU-@O@;dwAcZke$>&#?^)y}#kMIoKP@r+l<$oA|KnB~3 zW42sO?k+8rHk&mc$xk)ecH%EM5t7+d*5C@bC4wz08zKCIBMH2Rs?fuiq?#;j*OxY> zQv1Q^WS4zOldLhEJxk<^8|(T?ca#(74?5i_;Js%^D?9YEaqa9EFkZ{pqGqoKKwC=+ zn_83tCatg%NfYXYKvC%2h3fPWF@SlBab6QqT-OtVbWYe3p^n4>8tCYq$O>;_|3KF1 zclI>_P)!ZUDqRV`>#aD<35fzG+h07U#>B*qi-ag=B(d`tilLk$8QJUvdLs4( zxXiO^H=QFF7t=|<0Yr;=h952?@YFe@Yz?ozEI(8H)6%^vaX^-U`xYUfC(!Oeln@-w(}j1@CDTl&ooX+ zefgAaGu4EE<7Sp2!8x0sxw5jUFI@?PYkqUA;rZ?Z%66f{BQBZCoGqY;SY|QXtOb^z z7uV(FLB&fyUBs3iqXn*bn+6g;v)z$dt3TQr&4$}~F?a%$mF+1)uuTpfzRQiOb?sji z!x&?04K&zzX0pbLv0rcKDfl`X>d{YK8V+g6lqF6kR&$4-?n)m3)v>{ZE6oM@d1wAi zl=mTjRI|c(5CUE>)tG1|QKEx%2yOzxpq{1v@_uQ*V5U_{rgF`lYQeZ3R*Rsuj|}w( z&6$py6D~O}e`wOlwm%lb8Xk>Q(6-7u>lQa+jInue*M?0&2-2oyE00Nu-oUpDop7Bo zo!-gnJ?FW6jKG`V&-+vckPk|tb$bm)jLBannd3zVXI~a6`}X8TmbRjdmPS*3*ZN%G zhj#$wbbRo*9HtdOy8fBHxLP^VN>@^%4sl5xag#nm>#p>j%atL}mlFCbmQo0@y*|8B zAMKwR@W!8k7S)x__!4e4C_cQ;6t#WHHtV_Em;6Uj76vpc!%(e$k#siP#GKu#=LvuqJ6B;$lv5E&P25@wMG{$q^E5q|CDM}V#NmR|@^2Led6NG`<2`si;NH*8YfCMWNP z%W-pW_w&^xV_hVBx>pyD1FN&j?tI_Ebz5guhY-}|ME38;zFVND+X_?Yp%O?wvZP-=7_mlxU~$0QBC z7b|=t&>|}sc@&ACpdwzB#+TsU`(xLkYx%P|W2LU5h@eu#N!Zk6`6l13Cx)*1sCZ3I ziJ?z0bH~jjm%1$1E`~T8g#bTu+0uSsdC$#0g=mqIS-H6%l=tH3o~z-vpQ_TI3~7Y9w&Ay%K{%j*Hj zgDMc8Ok7k5=Fj%H=Hxm^!ZxrkOO=Ftt27kyUaE>oR>|a3uDSx*C}%eqtb)wx)&n#%5usVG$C z!`^5hZh+h+3ng~x`FoFSqy=>q?9F$MA;ELcG+}K>4*$hE&dv6Iu`HAaZk{%$#O~-b zw@(XLcGHemn%!Ml!!=RGV3n|;jM18jmR;;j+g@_!j{Bm?dX&W(`TFe_B$vzdhk3T; zh2`h#4D1xe5(e*74>+0EzI6TZWppPtgU%x1)>Bz@X{^f2;)Z@uzqx{*0=ycrzd4lt z;m^fshkNH!_Ph_60*Jv~;kUObM=AE;0vyxz=2Frz9rO9fWHbybf&x-6{154Ob#~=r zGl$^qAC(ZKi3Tfq7%CCcRP19`Lx!f&cSle0-4@Dwb7{hH!8~}n9rKk4&j!{4$5@Nj^+}nC?f1w&(3p<=MNRIVt`|&y_arA~DD;6QwCvJcs zs08+Wlp;>10HVCtS8)n-;q{yEHP(8Wd1pNoO^jj}>p^l9dA8M^``cf~4hPocTpVu) zujQLkmeT>%#BKA=Gjn;3ekrybx10{1_*xF7584DueR@7I7hV3Kg0`2YvK>@F8dK0Q zFBTpkl&!2zXEf7%g}vA2IInoG%UB#+wVO(F!&8SDLUy)MrGvm9(q1VGIVz^EG?Q5W8u$CeOmgEJNY6Pmy28wG@! zyzbZ$b*gXuYPfbDw}pZ{u>3PQEJUnm&?7Ha{Fu+rlLyn6>8f5XZ)xm&*6*5w5U?)) z1h0wRX;HtGsp1f**I_rD)DJ*TyTL=^4xHZadOvEoO(B0VB3P^l74GMP*{M~;?OVwF z5#+Y(+L8>Wh`Ob`v{y@-pl*>vz?+=n1AsCW4usNEidpuKfqtG{6Xl43r+RJPqOuUp|G`R)35E z3J;FI)!>(f8^q;0wA zu`;6SnRl}H4q6TeoXtWEgJ%w#HfDMcP?>9elDktw!8>Y5x_5G3=`!dtDhc$7}sJuB`&&w21A!Vey1=3T< zxI2|GakJHGUYq(9zv87OU*>^8&>O{JIupdHo}u?z{Q~1 z%f|oniUwE?dohCav{3K%$i+L_xSL1he~xLmFRj(DeXVDFZu6T>`J9R|b$>aH=8r0U zWPr>e|7?K=AcLujY}hr&)AjI`lpmvDvh0QE+?<7hBT>%INMVO3eCQmabFD)(rTUo0 zn1`>e@AvsNdTBl1MHct>*yIEJtSwB`+*`Aw8D6XeA>F_mn&2O5V}Hccp63tUkFVNO zTv!BeA_E)Ox-+#mrY@(%-LV>p2QT}F4j)|Wwa#Y1F?j9l1sft`-riq|{mfEHN2xAe zWLalRNlrq$_xn?FhIx#rxU5tpt*9JCewSO-Xl_A5f*IfBI!rfe5GaPEJo{-9w*ium z@8^_R-iLJb$*ji%5KosW$~Xi#nJZ<>eH$*%jN>F5QgO4P%wxSFS-$GXuByO{8ng7_ zli2t#WR#&bp5fTK{RlrrF=>==f%9oz~}zg@O7{!3Nk87O{yhBuobHY`Y` z0V5eqC!Mh$(KFbHuL=|nd2EgQHI4nj*&?4KR+*=goTPG4d8IcDr$y>iHY>}9n?-|Mpy|vF)xHF_LP2%6DP-1VCMvIFo zzb0clOWVJJBVFB1nk)m4gcz{REo9d+W2wkMkzP}}g7-4V(zcDGK}&OP^Q=M_Lr$xn z{sx5?U;pM-XPJSZzjQB*6X)p^g}*pyw=@XZkbvk5V`{F7k=OU`BXa=xPy@|gQ@0WO z%aH3i3ohLyd@)NY^B90*SxJH0?u#2&Jt>F&NP6YaB2_O}=c}k#OY7WVfqe$T0l5lr zUjEc^(v}RSKe$k*1aTBdF*Ct`!KNvcrS88ZAQh9J3?@yD30|)&S>swI)PB!s&H}^? zL7m6m@x<$TsyojFY~?>|e%cOU&ro?ZPh-!xwGTbF(n#9f+t^%G!tNT+xz%PqIEDKR zj?+EZnBih^tJ_L;^Ojudyn?ru{QJK^x)ZybUs^j+Z_-5&^MYup6VkMc7 z;UkoV%9zisAk)79!d~SkRg_~A=TpHdA{ZL{y1ML}nNCRa^rbojU0yG`Y#DW3G+{YF zJkxjo7p632Cna@`8d&?)Ny{2%FgiON)D)OwX@YmpEce};BtksRkP{qx_lo4}vnB-n zC?V@j9lQtaf`fJaTJ@!p`ds^6_~4B-iS3oC?Cqf(J#pm}|J7la6r~{5o#u>6!NUg# zflktR)j{#Vc&?hP^*|7~b0}kb72`~$*YB&-(Wdpyy=0!f6%LbbTw;?Ocwbx7+?x;R zIvDB&g}2n2Talg;YF=ui_rH-2MU-#u@XqC54pE*vP+1u(g01p>O#hRd_}MSiJ3*sD z9z}4}7CP%z6z&qg0H~%O19@}i*lvde{7zxNqzT>ox!l34H0*-4Tj~oDsj)w6opK3N z*OYZlFU8-@#X8u{MgNj_Bi!8lV{f&3Fm~_-F*HkrE;1dg`4~EF=`glhVz{ zKYy>j&E7aw=8v9c#NvpNrLd_-Em?f|M|1GDQ$?^i+b$#jfbF$%s%kA9k;NDq&HtbE zdemS?{};7?{%d>ipF0e|^X28?CTri_d)WnAf;Czm7a;}3?@xtCzxcYk<(t~r zus_G|IjYR%D^h1lZ2&tSgGQ-54ormhuPuio--uwgiVKEWi(Qn~S620QOwB%8*yBBZ zg}IV<)F?3(9m(hUE*4zQ)E}+%^LqrqnwLLoRO!}gG^Y(8ZK7$XOY2;LqCwdnaf&E9 zxx;P?%y1??y>L}16`$-AlD%qy-3&3S=+5}zTm|pbNQliWix)HFxS-`b8m>J z*VHUG5FN@>7e(4-kyXm5pmG4?%KTZ}b=kZ4ev;L$TDpg6tlOZ?emPM=yKF=Z@pyvc zEze1;IwV7smy|xwP`)}UQkMZF2zmf=wNEBw;>nUeu;+CS83*d^Z|B+y1|Cx5#!=d6 z%GTy86K;FAun76Ud}gb2Cl>K>Xv8*i{_U^Xa|w#gz?NC|JVmLgcEq=!Cp}~SH(0V? zhOQ1M6{i>Fx|BO%X4~O5kqKXoOM2i4wneX0qXND+QhQqvUIv?a(Ws0-K(tLZ0dE6= z6r&3b8IZ%ZO?~lY7j}7Kldk>6#&^2Nwc}-YjGi5_Tw5XJlfvdRR{UN9!R4- zLRb6&4MT1NAt`YkXzlgmX2#O$V$gxlNWY{hf{>QxrkYHtrQPc5<8qDV&TxDe-c_aU zxrzVJr0aFTvtr4I`wt0T510;18lvsdf^~O#rQ%nAB;_nrg1vyP=E6m$HO_`IAX>@U zPX4kR^BYXVL|}eoR$aubwscM zjhKHOe_7@KRhHonpMjL|DsAXoW$8`be^~FY76T~%hZ^nw1L!}5)L+8}p!~z}|NQWO z6A1qM;(O_F>=5cD@Wq0oXl1t$oBKcVnd!e-XZ%~)@jvtB>VFTGQN`{b%Yu^!f}i90 z%`@S1E}KJ{ctrZ7%l5B!CCXq;t{B zoVj?9gN-d`1H!amsU9+oO7cnovx{0K2xj0vfc=-pT?%+)T0yfy1;XbWyI7-sS{|~S zKxu!@)klFi_*k@YiRbdA2KKh-|F~!TE%$|%F3{OQ15eO;Ezl4j;G;PRa$o;msoKLA F{{KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0001#NklqOd5D3nJimO^8!Yt zHuoI3sv8b$*TsPWCw0t%-8gUwQYCj6EchwdV%@RA6(C&Z9xm0URKFB1)2Nl|zdazP z9ShbkQ#<}1jA$Jr985Z6B$A5+7;DD`2i~`p1Lxdv;9(d^qdrawd;1 literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/images/bg-nav-item-active.png b/cosmic-client/cosmic-ui/images/bg-nav-item-active.png new file mode 100644 index 0000000000000000000000000000000000000000..8b7ff59e6dbcbfc828aa87bf3c7a5b93d97e018d GIT binary patch literal 2863 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 literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/images/bg-network-nat.png b/cosmic-client/cosmic-ui/images/bg-network-nat.png new file mode 100644 index 0000000000000000000000000000000000000000..532a4e7264c73ad68da52d42dea082fc20613836 GIT binary patch literal 11534 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 D;3f6- literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/images/bg-network.png b/cosmic-client/cosmic-ui/images/bg-network.png new file mode 100644 index 0000000000000000000000000000000000000000..1a3e378b49cf4134247de446d4557f9d76ab6480 GIT binary patch literal 14452 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 literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/images/bg-notifications.png b/cosmic-client/cosmic-ui/images/bg-notifications.png new file mode 100644 index 0000000000000000000000000000000000000000..0fa02edf14cb589f84a0f5fe3d9a52bbee70b151 GIT binary patch literal 4074 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 EKZy`fg#Z8m literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/images/bg-status_box.png b/cosmic-client/cosmic-ui/images/bg-status_box.png new file mode 100644 index 0000000000000000000000000000000000000000..7d54776668c9115c195b6cdbbb42c469b704762d GIT binary patch literal 2827 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 literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/images/bg-system-chart-compute.png b/cosmic-client/cosmic-ui/images/bg-system-chart-compute.png new file mode 100644 index 0000000000000000000000000000000000000000..36c7cd48b455a6ce22736cba89d445e0483fd9a9 GIT binary patch literal 94812 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%0AFXaLw6kd9;6s8H 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�=g1{@4!9p%KMeP2sq9?shlUa62 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Ì`t_H4LPzpa(3i z&SvYn(J%Q{k?~S2WaWb~wp9~pVE2f6t7pU6EdzCl`nMwqyS+Q>qnv zqnp2j&bA>I&)8sZGRLI#cp3ipqA*DP z9bf=vuvHTh8}b5R1cH2{GnRY3j8g7u=0n{-yCuds0#IwE=?s@G^u)aZqe+K=2Ot0X z$)A;LJ>=tXS}=D>x_I8_n2C+fs+p7qm7!|D4-Jgd>=lcoVqRPgMoy$ZIY^9`Lp!lZtD zJL}zG5%Vnr+7zo<~;a(4PR zvpAemy#3igKRYGua-{T-y(KxJ$LsUrfxrxJ!{;b5lY~WtQbFhzLQKhqwaT%@IcbLy zSII=3&!PXOa8HHK!Moz})agHZmsHj)*0dkrCCq(y3VLblt(q}W6cb^Z-amTIrJ!0z z?mcf1mG%zzyd!eXX)#$ffkj&#irndeZveeksfbsBB$TgN-uR|eVF=f}C95;V9awqS zyH0MflFV<;f3?K!eZ+fCi1MFGT5O1a(oYUM{EB7c_?*SDU2%v3e$nzu;F79su*eM6 z%-SYE-hQ3iv_k`kAfP$pIR$`)1CX4yR4F2~`$tM7PDO++u2&A%|3w$MotzFXfBUWP z>Z+{Z-YPfh!{vyp2~ZM^D=JHtD2O0&=b@f|_w@d!faPoc@C3*c5CDpALwD3&ss-_? zN#s;gcvdqN07#ol9XoR2gav_9scpm#gn#CCX1qo5RqC#&21@*6cJy9ULojJv9zxlb3iIRwU zZ8u?AxrumMHdKdy;ruc+*`Cf9m;z))#rG7IJYJfV{G%;hj;wCL{drxOoO_ZNgQ^fGnD1-ZjMKmn%fp=N0@QTrVb z-Rxh{(MNmpE2%G$3+Yn1WlnQ(DI{Mx+;2ZX;53E*S|UeOFg)Tcu`+!^PnR|`q2`_2 zE)y#R+Jweg1z}1un8L@%%M6>rV`D$N`bLtwJ&!A(6N*g)<($b}P#L!&scPc6_Qp&2c}YlNcwz&P_!5$cKX7Qi*O{t+Ju2mKh_WhT#YP|@iBd@} z!=yykI;G`@*H$dZr|*wI?a$S~Im(k)M`vX`D8P*_{9b!NkAb{cL>3l`a;Fs$B^*~a z{R4p48#2bnXbCny|5CYJ5ju7pcy`8h_ItJ4M4VL_IVcNPoYsOJ77qV`lk-jgp%?#o zie-*&^`4C>yRER3yO-*tHE!XRx>`PmS1eC}YZYNL7n5Hc^2S=-e zV}9=1{D;4Ih@7GQ&7ZS2H&5ndJa|kBgq#&-lV7}#b2VkR1TLUdLM8K}pTKbpsRpwR zg!QWkvw9N&5p4q**FY(Q&i3~k!+?}ciEp{r7FmK52Uhr&z8W64GJh!6fGwR-iMl5) zsG@m7Pl`}9Y_GU=tZb?`9<(8qs{X~uO4ujZa=VT?B3(&{P#Wj1>X_*1?St1g-XrXP z@%5H}QT|`F_slRbLl52EU7~b1NQ0Dw(uj1|(A_1{B}jM2fFRu=B@87X($aN%?x*Mb zJ5R0$*B`K7>$CTIuO$OyG?`dEToj2k<@fFN`&a4~79#~mON@I5QwSNW3_aY2ImV#d zBBeZNe`B)H0+eWkaUr#x#rMHIQh2CQw9Ye+ypzYYyD+xW%1*C~f?su02-#d=G2#_6 zNSQPM>!v8J-qllq8ALa>LcDQlT8>Yq31#g=!Ab#`Ab#>0CY!b~!qF(-^hm%?gozm` z+5)_p!~duRe)bPc|294WISr;MLqMT&7t*8Bn23TE5*X+tA0H73hk?mBj0iOBo@mRv zS^sMu{r~?+9PT?JmA}-W($~%S7gtRZh^(jP%6j{ck^Uo0n_)X1Rv>S>@3es={#B@}3ctVB;cHV&KNh*( z?^kZ#8*tIWaTZ-2J6g8nQ?}t%6|^*L;P|GWm5iYtuZvzF)lfXmq%K^nJk}+AR|o2k~U(Sb^_D>w%=*B~8-bM#J&ov+F?!zYlvJ0FJ6 z^2C&Odk@BY>^D7wvd|ZrPaC>0i=3xmnzjVjmS*h>7kv223v&(}9`h{QiAqGRLzL1{ z1w$r%S{YP8`*x&h)8+U04!s2iJ~UbJMVQLd#;^mduNk_2{oe9<9>LdGG?nwUvsb3t zOmZ0gO|(56_#I#-u_G|33b_wgKa=MBTff=X<%24Pz_uh|4u;5n0f)=l`-b5~nUC{{ zvq|&H`vx$U*0no)UM!^iVz6sgwehM{=~4)`IQA;;IE#C#?!d)EBR;Ty=0 zeUg=VZn%tCoC6B)_c$x!GV8WH1|zfJL$^9X$%m%wmtWWK1Vbw#VJ>N5Yk^ zT|#_QpKek$+dVsimM`JY@bbtRvOBph0d;OL&Z#H#-ZZi z+P6nEt-VORD6_&%@VmWl^fQOeueTM=dTDcU>j!JIWP6_6bI7*B_kPp68U~EGukTl1 z_OS?U|2Tib?h+?MhThl1Pd43pApti65?-RX7*9@T`Mr1@ksDk@$%B0H5wx!~H#UaZ zqr>?Mq^{X0pHDDZff#L@Qs_L%c*7JilZru2r%(31TY-{8{?z8p(E8#2XAYpq$(FzA zc{oV@=@wt?RABm19(S8SN(NQ!Q}|^;w8|D893Ll0jum7n*SzSn-XdtLyrU5chs1l* zfThA-NqgkwnaXH+F6mR5;6bcrErp(0T=&J|6^*`1^gG^0rB&V=u0Z-^gM28v7YqY$^ zca7aVHCrnzW*G)Im5J;I^MKH7KmyNZ@v9)NEYah^bc&inio~_b*+vd^$y;U3_2)Vj z;#v=4_08rZl!LW#R|j>P4+khdqJy3sd(LTJ0738wpH$Y-uvZh;~~A)@lFlx*b~ST-%1N zdIF~+a$44j=+8*UMwO*gx*1S^-B#Om4WDbYH(4-ykDIVYAU4Mr`d(Uxyw}0>5b3~n zo}o?_Y?~j7&WchD4UA9^*X{_*;H=zD1F87eb0B2+B1*hui!Y@+pZL}bc1Q=} z{^f8}{iIXCG1zZy`%~OXa0=qtK`?qMXl&v4H~ZBzo8lTqnVE}(ZH+FB$i7VO+!R%1 z+ygYR;1IJ-4T;#7mU`ys^fv`;@EAfoby$)dEeO0uIKT;g8p&rcxuy<(3R~oWOoyp` zHxSeMcFt!;6UJ+aX>W}#?ZuXaL_q_f?cPoM!rWQWN+<{;odRd-oZ}kJ(ZXYj&8pJMP9CP7klJ-&cC;P-WU`)PB25<=Ei|K2Cx_uubC@A(xV+?Aq8-Pnt9 z`t2Efnj;DFW-|*UNE9|bNe;7HGvWntGG=#0a(()jDA0S5i_E<4eXAfewW~GZW>u2v z6&QcfLa8X&!}hj6y?19wHdk`D)rqC5|8=+C%~uT+|7(GQ`-iM7A^VD!5n`QS{DS*4 z)SC4-QO5wMN{(Dv-8;F2Z%})SqMSZwgkB?t8|^5Hs$Tsd-IUJ@|D1flk{4~<(_PNS zn@yKCvv+1U+SjKk2ommiDpCsIXQ^ZMUL}%(p64B>z0;))qoCDPd7&Rv_di9Gah6&OW_%52^6J02g!i4u_a{U& zvd(LUt@`_Xk$Y{vNWMYaviP4|06(^b*;JzXW8$^?0l0izS_{|J7-25~aZvQNJHgFQ zL_6*!pz~wzqj`u4$qynzj9yUs2T>piBJoW9qs$LpinTjJ5Y;TFEe_P~Q$MdCuAb5t z7HdM^*eu7{Y1LQ1UNv41#>v_;i?`~FjGw) z4+frEcQ5t$CG_8%SntoVbc~X}x#VU+UT_Uy`KpkGCP;?#+1fo zo5}sk{eTR#A=UMCNU(juI;{l(txPVkFi&Br)KNs-@1IwA)swqqs7pP`zpbr0y}!3u zcjTzRJ>#6vV}!`~T!$-%uXIjCLz5`HLHL#FlalnvvS8SkCMG2Y*69xX8H2JpnaT|1 zxJG7q0VHb!WwlCt_K0=W;l!%m@Aj;&9i9c8)-tu9=j$1r7s&$V3Eb`HG1=%J??H%0 z4WYL45pWk~KGNxC?3$n}P4(QZ>n+7t5c`uUUQoF7vv7Uj>>=Rz5fh#{7~^#I8utN+ zsC-964f&@~B+v4Zwfn77G(A~U-GY#tJEHwS`%JOYs+Q7bM+ReDb?;Q0*Qao>cjLqH zl;{an5)Hu!b8)2)buDYnu`96%&13J~>7~=jH=B7oGCYrbc_P{V7n&h|cn0E!wDEgw zlnoAc+J1!-Cj1sU6~T`TAYhi#a#>WR^5g7#gbm|(qU7{sDn{dlIg2Ea?(+YMoGiq3 z72)X&#XUyyMFo*ap{rA`sD3*F zDV;BLqxJ(L#Za} zUVh%hqG2AJs%H=JT>)LU4u#g((8BufzK1W|4))Dp*3c(x1Ltq0l)yrix=dNtb1{7vmXg?(DDJex;t~n#Df^!cVtPctuw> zfi-z2|BPys?fa^L4Rbe&a=bWsd^PI>oVlqjBr2Du%g933q?X~JL4WkoPl$osHtX(UAIyDuF0CX^%sA*ahE zh2BH&y)DMlnWkt-_hJGv603m#}zvIod8A-*Z0McnmQhsipeWfIEtZH5C|)SVZ)vhdI6090DJaB0#}Nmx87fxTYTtd8k#U~BI*lfQUOfETy&|Btf=3bDo_8A99LP7to{M7o2*7= z5+B?r6QS%$n^EVz-tqj5r$6l28E%a$Lk)rgPG0SXM#}=S9H9iq4=WNPy)B?ej$>x; zyGF`~gg7#5U|=V$_jl!xD;j|RoG~Y7?6 z;pzUYD8v$47ue zvs+f!Mcom3l9DqyC=VUgY`_+vnH-~adt3FU|3$txJ%+vQ+xFE zXodzq94q;bTI{Uxp(O{e&fbtZZ zvMXfv0pa!cp(xzB$H^LyO+w)Po-=g@G0Q|gQ4AAi*n$pH(Ep*4mP(>m&8&TDgV7zg zZTZokXu%%H(n-39-+NLegx0_gNkTH*^|pT`8UO|oGdS`Wz1lQs+sMa`>zjPekUH&z z2U?k;-yEG7%)dGLlC`K;o3C6G*5YP`nM3S_ZczKa_pKBfm@EYC_4-Jy&TN$CFU{YE z!}r}KLJqnvts;h4`cv$Co}pu_29qTFDKGJJ5m^MC&Muy194_$a&qQWj+M%<{#BZE} zUK1S=R*x;1^pSli<31ABHF72p*Q;W8zi8xE#dG0>7+t3eG<3AR#=+7~NiNnVAS`(C z%=k6E`8$}cyve-;rkrDxEF(z6EoB;IFV7dvZ`0rUZh; z0zz?N&A*2}4&pWTb=;W0Cq`Ks*yz>jDfnfa^Sw(`)${G=_bhmI87Eq>9Bs6}{Fv14 zwEUQ1)ZUsqOoWx8b>}%>N*Zs!VrFK4Vs@+>e9YG0O+zWE-d_E4u3vy&H1zPP?7J{H z^TSff;=irl9}sO0I)gRFkvr2iEjdd~Ki>QyaL5=e1E$wRH7@j+;AtS5U#aM28SRLB z)%)A%@1-Il^aZzIjEUPZ{w%THyMU%O)S|v6QJ8NuBv|0g8eD`F52NEh|L3aWg=z{P zD*6(VzOXYQ>wCFJrVn3z0VPwj#FygX*p5*5rLQ=Uj9U>;pd(_Y@wH^tES!r2ux|j- zl7MnU)<-6PXuk;)8Q_^P>KL*N!M3k)W_Inq>dWg*8wvXf5v5iCh;wws= z@@Kx;&qlH}KtEqk#js&f=@o(oKUfUvn2>CB^UxMY&+%ED$)=t5*&ynhc9wx-+Lu`} z8|_FYCIfoA)VBWfy`hX2XEydZd(0$yCX36>lh>*a&JT1dxL??Q3}ZFfw7T&NyEs`o zQk)C!2b(S(v+jzu`8e+i?zRXzZi-^%lpe+mcGeWHO8Cw{JVpkFA!}O0RD{T5^`+Tp zqI)y{wH#0UNmF6Hv1$h)hh3e6Fz4V~*$FifXJKtaRvx`KIbPDpclgXtFC`0n>|Tkf z2|Kmtx?Fxpbv;Y6GE9oKDZEI%ciVrD(>t|amGv~Y>{hre3urvCH|N)v;#<9%)ULeWgC z3*rxObf!`(>h0?IT{(q74dl_(uGBO%9Sp>!IO%49fuhi<3sxcz&DAQTJe{DOxxL@? z+NeK9zUe>8k^HK+Y1oGUU7Gy-FZbb7I)E=wK@)QA`NWV(Ed1I_6^ouWEOF49Qw>TJ zb5EImZ%zrL{C8?AMl;uthxG&Dof?U7$O1w66{pfLk}B%X?)4IQpu;TRl_PJ4m8(62CHFX4oET5E{bf#EY_M6 zc!xcV>UJjqhmB+jrp}zMQ+3af-{PoCi1w?2jL}|p*%a7lJ>8tF-ymGjUv8+W@<5t~$C?Uf#eSJ3ywX%~Jy)vfL#& zQD&^v{T)3+kbFn{&jB)5w02aAKj4=hvMW{&c&Q{d2OMR$a%m6OL#U^Kbw?R+qY~v# zf@9xx$xcCwn;y#E(${qLM-9zH*>?fhIhnryVv5R76G#jO8L#EVn&s`iQ14?C<15Ke zv?5FJQVBE+ZtKDdxIP6dHb3U~4%)`LwaD2ewrj_v@Oa{U`L`<~;$2bx)4%j2 z2l8*OB1x!uKIW97Z=A|sf7Lg{bJdbwHPMtkU-6v<;;K)V{8S(t;8P0UcNw_SMWM#J zh%p#po&T`~S&m?5r(~D*=GFeGL6f^Wp*_%{%vducJ5DS) zqR(7Nl81c!yTs_~a^5;SP3J7S_dQ;g890=_m3zxwby*95e)JC5|AUJUJFBiRK>f{Q z((P90Vi*U|gu_SA0Vsk-jz?5^XHw+J7!@xUF0~Bbv9P$fJgc=3pYR4y^7ClEUIQ^B zli2AU8RkhFb41FF+AK}!nj15=@IXQ3H?l#2L88J8yT-w9-BxqsOii6HrM>&E-AaFs z+zS73nCi26yz|`JhELvB(a^yuz4Atg25BU5DG{jfIql2`52$-VNoDPCt}*mP?}^HdOU+vsyoik!WgbN_Sv-u~CL&VIx_O>p|4uo&-2fn;Mv zff96|Hf?p%yrBbAEu~V&QN9ioCAwPYe2gW+9XeX=x{SY^_11g}SlvQ7@e8T)p;FvU zWRtR7f2?^8WS-Kg&tb>VqWk$PR`cvv2Ro~{wrWD=l_-N(^yEAxQp3A$TPyensLm=E zl8bB>Mo=|m!~S9fx|anduOM!PSjK>o_lrrtp*r`7sya$Sg~ezL`BvyGis4Xlj80uz z>Eh%PzE(!F$hH;f08vG_L(YdSVT9Fkq~D9b1)&)E3`$$WSf2G6T{xLNEKkv z#o8qZgC*xV6=P|AIg)`bj80U_T%D8*$B>Zy`vv>N=2S^qf4DaFycLys@fd~Ab=k?R zf}K=zOpno^`nEYbo7v~H)g)l^3N5ZWoDU>6{KgHKr51$J8#DPh!HZek5%?!yM1KeU z(%mH#?mpwwR}0MxCo#;V7~-eG-dqMF?6Za5qH5A_(>@2|^qwcofrlfCp8b{(3irD2 zugY#b2j}ZSH(0ys6>qAwU%=r?1%)Nj=K5s(Xu5T%72T|P+JhU6!e|F__!PTjoXOkV zuf;a{F)W5z$4HFaYBG(B$G`&F%V8g4)^bs=dt%9Pbl($`@?*!rF}?**05LtdqCCi9 z&8C)1PDpR#uLczK#?q`_sGCU=#wyAT&cPM(bU(jD-T${#ivz}C}Fk~qU%z@bF0ln(jb$zULGzBx(|@WwD9i( zLMTkvD@-Z#{O8@(+9VHWb(L0GEZZw!Qo8&;^7w;vGcza1&x0x=S{Zf z9Qw2nY9*54fBHF2Nuu=7v8KT^h{g1Da7}ABq1_-35`37p$USItlz6&fRCJ*T0zVfr z5sh%|RfUQQ4<_DZOt49+8&p+))rO_;g5VK!HBNkrLCpr;xrogf zdZkVEN%KepM8KW}d9MERB%6Kl2)}W~ z07b_3KbA@VbWukfb^kH;=(nBkOXXo{`vj|1h7*LLtufpPbqr%Bhb^{ci$ zJayV|_=Bb{|yb-c1ZvxxwDyIgFn3>9)X3C!Hf*gCuOwbB)_8O?hRM)fiY`EC6f3KRqEhbMRvV$ClLQVMBvC>c zz?Ap#gl85AqtnI=3~uz=VbhJie($QEcCFy+8i|B5#jWiK{=50+--WsNIX4T&0Wf4E zAHaiv*DenP>J~z9Jh!`!uvgBc?3F(g;u>{p z&D>Zus08k&Pn<1VxdOTlgcf`g(d%cNBH=!mcU3kk$K|5A%}Ml7%6coHL++q@9D-SF zlBdbytk6|tr?5;Jy-e-kB}2gSCv|w zH15EW%E9svUosgFs-6oF!(?UwC#kXCuhZO$3IE&jTz+Bhpd@To9vJ6*M%H}Z87n$SHIk>?$MvrbI2 z$Gj6LoZuofIGO)w2=uBZOzO3emr6p+c-58?Dgee_R$bpFgyMsWfw=a0^xL`3lZDwr zzpimnyaI6`r!Pqn6S7kM@75Zkg&g2vZ!VVpEbNIyTDrT7BXSjG3B3RkYRQCSr0 z*GLot-(AaM%eoqJwk@Tbuy9QK@a7DVV$69^LQG~hF4-%s0`NNjnCFyO$t$+ zR98CG5)EjfW@9F0Oc;HVFElDZfYa>2e;qAwvlO6Ti-opFA$7r$+n+Y|wnMKp9j!O(9t@3p_MlAI@u5**C zw7v|avqLJOs%msh1K?`2`Fv6d8w^*w(H~l(1t8Y&HS|0{y-k=lKo>WM;13`j9NLv8 z>@}|(^Dzq_Rd_==Pg;7Zk1BG{5)}uYM~^U)CQ346=9&gMb|X7~aQy%o!DxXo02C!T zbyxNTQ6EMX%2@pdcKPH_R`o8Y?~8^M3L;;^dHwH0k!=JAkjT+7>ERyEsPt7~68qUh ziHQwkAd&gMT$c!b93dR$PtJcT?e>b3`(2(llTvy*#D(yF2->e92O{=?q#;->VAA=E zSHvtoKG4VQa-B36GhUj97QINStv)OEpy*-D_Bnx4ho%OGWLZ=qjhA&1z>j%9Fjd^h z%|SGNJuqY?yRHCGpdCRugSv>c^>}RZ#V3Sc%HT|f=7(5gYJ;TvrR6BacrA)7bbFjO z!gXiS&$l=z(J^2NBP1~FjW<{Yp;}+cT}!ub8eIibf`4CTGt!rvj>jY+3=W(Jl$grb zw)`FH+OS=rzF^;41gM0_1Sr=Vz;yi>}8%j_iXM$ZN=kgt4Z5E5cW`Z11SVApM^o zoVL53eMyraCbh;lGku=(OOiBz-aFlZ+5l?AbK158;CS=o%Gea+lukGhlqmSI7J$*9 zhX@X|@sF=#f2fKh{tziS5NOFzH>qkY_T6CH>E|w> z%n6r)eaBqDiG`VRx3q+(MAR&={{0i9V%a;j6qS1M9*$%eMC;xzZE7Et)}HZH9$?L*=w3R7A42>MT#sq- zn2(dOb(EtocdXQ0xlM^Zx5cR6gsc%|75nS`ZPntkp%l^@#gY4GrKWes2(R*bJt4zkFsc(V=VvuBz$x4p`P4^@?%Sp$c;@_*dslbc&2he=lOBm`Sr^0QpUE zj0g$e(K5Frggt^C#7O)6{xDfhdDE)lqzB=N`=FpE7EiM}_Y7oNchk(iC)NDdi@g5= zkO|rbUA)eG!rdiv9&Na05Bm0asRo(*NHynQuKx8hctNBI!Ay&i^Ow(5O2Qo9T=Tti zd46WazpODJHFPz&?o;f5Ol?>fY1}RQtILkf_q|Q&xIrm}@Rpdgo_85=%pRH_3rFFV zJZcUOBsr4hDA?u>e<3D2vf1QS%n-PsVVK7Xp{yN%XPZ`xt-K2D?cJD6T#>kH$d@p& zqUhhTr;&zU$=LuqIk#if_Ywc@*XfP5cl2S8u(u}hT?q=opkN79LofysKJ&|mbW41e z?Q4{btIP)fI;)e-F~ zk%MD5^CwFUM! zw&`e~o0jU>drJNowb^@jiX$?NHH^Ik#YVa5Sa6AqPWqeO0Y0zP`1g6U4rLcKt12EnkJ zh{-n#lIhP4SCUG@jdl;_-hqU^G-2dF+T-9z^e!d=oF((`{r;Zlca<0`MpQ&{DElp9z`ntZ{ZScMc z4?+6x!4l4Cy#ZMwJX3nIr&wXQluX9rpY{D z74sr7x;i}U;ZF}^J-$;C)bgG1;*2ODXg`rHoJVSLv_?IZNM(WPU&$W|PL4kMM5-+& zt{g#O(&fEyNy|}O1F}JP9tYv9%zySoA|r6b`D-$HbX;Zg1MdCr4|$*UNZT${_sm@a zaT%>}-_FdTb4n#N6m<{iGTG`>H-OVxFw*ra7GyYYS~;;qIIag_i6Gk~Jf+P9$YBs%;VSYoG;W2zWX#oI9 zJ}n>j4SD*7x^$^dZ3Qhq9QAP29vG}H6c z!Kcjm=ndzu&CNuyhwTp#Xr3YHI|*T}gP8pqoypI7?g#Lcg)A5Gp+iUY9u_o zi8^4>h~3Vld-NZzxtOnyibCe4A*q`TKLM|~etfr1PLuc1X#W{}qM%exfRBNXlP5@b z^T?1!M;~I3tBW)2a}EyL?}J4dyrD(h`DtGtbY3R%tdm&MI#&gwy(8u!5b!;C=65cg2h5* zs(V;=$eE|&9~bj=u$4;~-pPKA3L`&}uv9E&Cz<AbAKsqYt4#%PO;jwo{K zyHS>*gI^jaMW$t?C*P{wd83VPySQ8Ezt|Jki2Z@rjV*0H6bP;di=NENhdoJj=ToDQ zlSlFpl$@iJo$+TrC!Thv2f>ca*6fN<2jFpgqo;p>P*1(5|BtuD+xOcif@jykH-1(( z2VMCTr*75ZnZNN*#Q{!FI`GVAY(%W({Fwy&M-NFy?fogECS5pQXTN?Nh7J(pXJ)T4 zxfHq+N$Z1-kNc@8{}x}a8G$ec^rOdyetXRdN1xBhma|px5LdzwPsZWw4jn`!Amn|4QEByutXC=WaQ3Sz`U%3bT@ zBl{-3X%@HeM#8!&r^s%^a*j-*g>%A5|E>#Bh9O%s_s*0k^U)DI$M(bQMmQSK`+@5M z)<`NOrwe??&LaOfbTY_a0r-o^%k%l+(X|YY_b~#GR2qggb^KIh%k$7jBKmyvEKZy^ z5DZW{rFLC!O$=L`IzllJA^$zSn3qMb801Mu%6c8!LroE>Gy=7M8q+L z5)25kT*C6EJsWO%Fxn&A?|Q!}y?!PA$vRWn54(7wSvKA*89yW4k)Fq&5M)&c$Kj6c z&2$y6A^HW#ny<+?zdip%uilZ}HuYxJcX6buas;qC&FsuXDowegc{Qpx7i##*S=^8Q zD6>p|^|-t}(v!y&wQ*?bou5XCfCCwK<{pp)Cq&rG#KHm3uO+e6P|tBqnv6NBri@rg zr;p7!KFJ&vO_)_&miAw*PICdJR$;D+RIyus3qT#kVid@gF!jD^o1A@&-WDea8@i4|L)c>T-6w_7D4=CX9Ws?hCy)Ig-%oHPqzRM}uOD~^gG z0~gV8NNX|yJ=b9&ThzBPdV7X-ewzM!4#DRkI}BdwoH(e54cDT<2=KcyBuU|^i!~NW z^(h233xgi+kvzXl=xlT|;`e1hF`H2fS8@`K7qx`fu&3ff-ak)O4@WbLoSd33Np@oyDqO~z#mX`cYKZVH~q6#zFd%E<|76ZI3A z08FW<`(`5No=i(vlH(>|$oC2bRsDs)Q0}VzMpNdSakbi$mB%&f&;~UQct3dBe5-vP z`O1SfbST^~r&O4*e>R%@LxAO3LDL6wz!fP$Tc? zaO+mE(pZ4WU2fztl;=F1ClrV)ogE1tx{3iiGcj7OM&~Q;T_yrQ% zl>kZnT`h^uzCEl6r2rg1*qmPDCQ5=&X)3lUZg0E*d1SaOQjk;4hizXdRUuAjT4xfkKCS`_y&D zQ1m40>(_Z%v_Il55j<%<_ z>(}c^1NY>F88_el+$eCf=<+g@tOE~3o;!Pgpj7mxZg}3jN!mZY0rtkUcD}o<`HKkE zbU3Fyi5@Wl=AQUxhc0SttUkU?Vd0vN$ae6-OB#6P8T52{tz?-?)B6GY&*Nr=630@f z3@f!)-)wau^IbE`OG_D8Rs_ZbeHFA7(jGyjJmDi$m=hQ zxP3R21!|e2SasuTpNBX$p%O{y&Z&O~Q+l9#c^5Cbvj<&bZNv@2e)NOX%A|>(Q`87B z9i?k8y7&FRf<=?Q&nk~N9<}1gz%Ei(wya=N-!JQpYW5yB5N|$`AW)oITg*d!<{2s4 z<1KqB;S^O+2q>*o{V0Wdbm}N@Y0Yv;iaOPwOiY41JsQoha?~@h{0=SC+>mM7HC$nU|BZlpdsi=-V_CU- z^kH@K`3eS`*{gD0nTn=;d>k{t*8xM5P`(1GupR|NxW_`qLl{W^kF}>ALi@pr;x3QJzzYe&;yLE84x-Fn z*T|MrAjw|9G}lFN%@)O$D^=|STPkQ`xc~Ac_34MNRJ^qPHW%ih9*JG^uZa@f7XCSO z!nV%1j+Ud^=O5O)feFCummLXV7zP1J%oTQW-(_U<;U6tt9&wE`i<8h2F*3`% z=ufI^`tVEt?#s`^&aA+X#_jc7+aI<-fmMqn^Le4ugGxc!N=cfUHFHl1`d6i6*Aou{ zWG|1aEbH?)Aw%;7CO^~dOjlEhCaZpQPeUHIka}pEgru+M) z*S)36d};G{rV1MmVW-RfkfVveCZ6M3!Hf>)KSy`j#2dO~c2Ur(*5$2_8T%UXLoPap z1@Qgfi6VGTBk!7XHXTkFPW%HmP`h~feQy(fOG%-fpx)&D{>0F}{gy#9m~!LtbpNJr zWmxIY=8d1>1JRGmk659Re^qdT*xRPV+1NI>6(`Z&w6qkpsKK)OJ#T?sBN2!BZMDm{Xw2W3%g5L{d1f$Mi>x;c|;DUtNT3e7}+ zZid)pIIMM`QE1l{RV-IC#p`*O^cK@s(;_6LhaJD7I9@$i&81ynA7?+>?-zK>o86pOR}rS|eX; zWx~SZZtL;QsHv~+dYDmr^5zg4W>+jkaJExdEluDT+J zRShB@mQ|CQg$q&Yg_*u@ims5+m`VlcqaqV581gEtcvY^WI)N8os!`mDl|*^d!VYZu zXuEHe+~DwK%w2sj#wlN3 z?=X^Lgd)zz^Q;n~=?u*3)U=DfJaFPp<^wgDc;jeQ^K8tww2|E)&sr}JQug?OC6~7Y z7^IrXO!LEN1@CBJXK2Z>dwB}P9;)5wg>ZMFXy<~{j8lpEELg=$nc|zsKe&R?A;=$z zLeU+cr7caNv?z(;1$^tN7Wdy~zUfOOU@s_u>_;;rbveo6Us+X*Ql5ttog$r;){xJe z9|?YLwe0-48VnOPtRbc6sN?LN>fSgI7_4ay&OO}w`0|x!hTn-W-u33NJ5zCA_xZ}C z{#852Rg1(Rl}BHhYwV~kBIJHwMmYH`GEetkJPbc(%ZmZCNRUc)xrv2+4jc>Pg$P}C z>e}?n{k!jNvrR@1xg5<@mKWA~B=e?ZZI+OgmqBPoBUPe}1$2@g2^mM6Tt}4Czhi$; zns_iFHeBqS3_&-$ozQi6qP|wwgt1CMV9G_nQNZ6%MnyNJxVjMfUT4lYp^9xk4ODY@ ztHaHWI~ZSzRs=*EUYmgXoiA7kG_I;R6^c3-O}W=6mwmsSfr+z6AXct)>+)wOI7|t1 znAv%Z%EhdQhyQnjnE(DEvLK@_90%t)dAUoWOQkOxsw^)M9lY(nr!fAl^w6L3U9PNT7e;Sa z*Gj-Izc6w8>j4AZ^FYB39vLhf(V)KS6T`EZwI?pUy6mkcO@a z`KmIIr&r9&4p#wi+0VJTi7)@LtA3RJF>lcsq-5UzPvGL|aw)w_JYXxQ%0v_^I2h?t ze{7M`98B-eE8wEcT%qBCvpI`8pml)fl<#WDJS0!$Q+lD`-XjNkk#yjKXP#tt6sY!A z`@@hZW864q+1ab;-gIkM`h%xJDWYFrcDg?jo}^VH&(n+vS%mZ_@%vaLZpb?0Y4QS81w zWnvfnG5Nvbji5QZ%5hD4^M_-VTun0_{_Qvl+iX^)7=#=WUPbZ&5UzZnJvvQBE6n^_ zICzo~z7U94FNFoJfp^Hceua_m&o{Dq`*`Dj2yNqj#OlRo4RLj4Ir%!Ox6dnd820fe z225HLhq@b{UA2~oS)d5H+~^n!3Ry6ZJ1S^l4_MnaFeWk0j3^%x9~s~!1HGE`PKwi{ z_Lef_sG~Yv7Hie4BGx2)L^iB3#>et^dDDlzesiqx|IzhTZE>~1vbeju6I_D3I{^lF z4NmZ2VQ>f*+$CsmhhPB)clQ7T3~s??a1Q4=cl+C?uhtJ({dRYCbrnLvV??a=r@!1{ z@qbS)KaOyG!ujVKxJmR0Rp=Bd1Fk~jR65%Zf_=2mri!S^yZwI77+}McyNq_a^s9vgfW!Mi(|_y^U+a{f+}nFXA%cmO?6Ubj12_@f;dcNVIKx zz9PI!dut&LIt@^JwG9}r|&1C zh$@OxZj+>EoS1loCGcI@Ipa?iEYLIiAXtht$d9c@R^hl%NvsI@qtTSmd4ckuN<0Mi z1U@g!KLZ`wtB_VW>_oGfXnQ5ZZ=hlkuNNT?{SPFRx&!I1ONlGIl;dW1UQi+Q#jaqu}&> z-RiRu%U%PiEUGVGs>BLND0WUV;J`?<60mYnIWMz9NWul7Z|%E zUl;r(R`kHd_7nMyCGt)U=HRoTkK2YeDYxz`6j{Ls>IcKml!Y%6CaQSg*XoJ(bRj;I zU=iZnkbdl8z%%ye_tO`n?b$t#gPFM8SMy=jwglXKQuOpAO2FsBX3n?k-SVagJ+WYxRQ>b{%ojFf^KYh_m$C0wym% zZ(Cxe+EA#gd}K=<>vsPCgUH?8qq{fS7JT#UADrG?BkMswsD{6Cg>V;|2xdi#2hu{| z-(R3rqMgiKYvQP0t%HtwY_6TI?hqLG#3Q#XvWyDrtc?xLCqg3d={s4FDdmO7bffB} zGpDS==DNZ(6YX72zkMiI^5J2UE}JiW`Q+4HC~3%y|IP&!DL zKJ5bLO`yivc58NdWEeM<5gt;u@vP|$j z%E}rQPUTjE_$g?@T6zNuIgAb#*d24K>VpkpHGbr(%RBc9OPm9W1r)C9JQ@r1{q_(` zP;EH4$mC`Eq!98rO$v|>cmI@`6X)mRZRTRd=699ND3g$X-F=tua43M!wI^R>JrS{o z5tPC~(5Lq$LFx$)?|<~nz_92#Y(ql_aHhv(4UE?)Qek)n|?LkWw(Rb}zD!xFNU=Ol&lm(u0`I zjL?pra1cs;GI+O5++V%nyAKiWRtErhHmlb9*Ox8j);3i8BSyb*lN_$38$@mb8&P!JT!Q-wN z3jG?}kvmt)u_0Kc*Sh*0N$N#Lco8n+kaSIb&D+cW2rQd>qq~ObGf}%o0QS88(ZuEx z_HdID-EuAHJZ*|F?Ux?bWdhF_WMqED7Yy}K4!Qwd2y~uTaliY5E26^D9ZpA_i`87T z=0)O+KZi^|G@0e?&4zOmUqF{^foBLkAo!362nm8PlBOk|UXU=8^-JG(8g4bx29sWX zF7~%{WP*25!wa$BkD)&h5CAm8z-v3}@cy+F+8!=R{9%R+&&T{Uv9rBQso54WglNApZba zv~2Y}6wmG5gebApddtjgEEgd&%Fnt(dYQXSSsSY^Ws*L4(9(Q@}An(myZLCZkok=bx-% zZJ&IYKx{v!5pfOST{rLRj*tqvl_Pl=&fj>+>P`=Sn|p}R^A&vp*CQAkf3#hxjNKj@ z=EBzhc<$+sv&}iGk>hF(QS`87!LlL#Y$`|-{{M$~{Bx>Lp1zgWAcu1gzD(nk9#V7R zA9Uf6FY6DFrq=YpPJPANN_S~{Z;||=p9W}`uHAuLM0sn4p*%{c=MfNYU+z~4f}j^x zIRma?8URKXVhvxiu>N|2|0r@rtMUPuru3%|*3Yu~Id!6G%I*=?WJ6!Wurt?#A+`@HN#S#5%L5iKd#c7%xaddHh+rvRb48F|%U z5rpbMxtMsFIgx#?=h&;$3y{``9H9MZJ|=m+FJz(xzCr+doVoxLYI{9lzO~t3mjbYae@{ zbUfX~wY6u*9c5+ywtU&D!%Eo-D3tUJy;cJr>`qQK5&T1D_4ZUt7AjpXC~?gac@_ z&^%a z#L3)?=pB(knWLR4e3}xFAL+)9a+f{ywx}UqW{evv!V>+7S47oQVrWUd*N_@a?5-hW zJKsuNS_)%d^PcZp$~`42Sx8Wd{9Rx6MplRR@6PvCt34xH$Lmz5bKdiml+J(3u90rP zhE1eM)uR7>TT2ioX&=6(n1BWkH@(YH^y&i;Av$bSGTYjtlRKw zy;5GQe%`h(4Xqe?>D~??8OJ*0MVywC6;npbmvpmPu>ja0#Ab7OvY0UF?XE?ft9Ne#O?TW$-`1ddmbAb$6M!&cI+Ic3M+Z%GoJE`jg ztk$B(G1fX=H-YtAay$3O^`nmRWzZu7T~YJ)MJC?pGyU4$tuwdU+LU-f)(dQx>uf2(hqX2!Gy0~zPt08Zz{eZ8iRAI66eX8tO zPzxHhq=<|*_>wEBt!S%GF-}fgHwFxf=j4c;Jymmgf<(^CXN!s#?w7n)oPQ4*3DaUg z1G3wps}L3%gbjG!O~w4Y*SI9M`T^|e!&7*uDG}0B{5Uk@0TjV`0j8M51oMtf=5UZ3 zcm5a~KZbu>d_$HWd@Gih`=8NiO>1tq3Co6xa)oLr;eY-CRvOfp7oN5YODh3%enllEN}OS*L8R=@&DG>yjD94LbI_P*PyF6!|}Vhey}0uRV$p!wSc3)z!6UE>|qgA(^FUd409u)Hx&35du^+S0!o~5LDj4nHz9bJ&zwOz+qh#DuuKfNqxqLK+oXk zslwvtxjSYe(TRyIT|mfPp!V;<^}uK^nZ@6PRTECAy7T28w3@ERMzDJBg7s)0x3d3y z!O{C8-J|TDrf4tJ_I#OnjVE~j=iy6T_Qb66JR zf7hkd(;jInJ&8g}*g+cyc3Bv0C5IvXf+?~=glC=Ci_3T#w+X+~K5CU0w(-g%S=VU% zwR~9CIO$c(adn|7EYG}$!u`CPrp1<5uXaPJRiH|!duhX8+-nS<$jVI+EY0-&s z$51SmkQGsdFeXHdeC+9%fPUoh4Yvn(_ysRxwV5cxSImF`HJ-@#s|lpREhK^Cf=29I zob%##|0fjPWP`$$_~EaZU~EF#6V^f~?fllt_pnpu1l6$>szZfg77)=d@%)oBdkvqo z#uo`6i~U^XY|JL6j^w8youzJ(ai-+1E4ViFd;QnG)2T^Yv6SYv^-U;Zu(>`@n6bSh zdMh8rvu{4c%J&CWWQS?RE;Z^Jucg20-#p3~{kx0$WDtO)+(`^o6QWK+@a-(x!`WYV zB#TL9%`apJW?E7rO5a9b;K44kmhn=wYIw^#UQ*Lcb>CsKPRuTkY_}!_2WUU6oVO_o z{uh3oN5~Ho5A{V;yZ_~Ba@oJNsPwp-+2#WwCY$u?{My2ILdp)HX8&SN+uqrUb7q zg2xhU^w>}wpHI0!8&*ttlk@YG)FnN9ZbGwkuZmrYS?H*UOS%x z+m9)E^NVl`#_$IRujIC>v}nSvEDL&+aB(?A@R2GXCAzmC4*I!rHn)I_E-OK}r9m*F zTjCGVtG6W?n;);=NmvNicH@tas|AKhIJ@hKoZ(+rx^<{FMGa4?QVQwm6QiuS{^yG$ zCkOuJdq2zHy*Zlw2ieK}A@j+BS@Pf62kSWS{fw7+u0#ZS4hKz}xsZWs@^puO`;T@) z(!G?6w~;M7;ZEeSgy%P|NLQ4#y6uZ6iVcjt-osqI(I10ktz+|9uXFYr%M^eO&>xW5Yg&N$x*(z55w*xy^zT zsXO5zn-SixdgVThFaA{Z7lto!-IHC{9^uFuaGhx<-n40S4F;C?-dI5sPs6a97;HgE zf0y#5gKki$0qLz&~xm&3jT z+<$XJ`YTUH$mN2y!_S83EeF@@d9L572mgE?!O`wYu*2YxK^pBzTu4EOPdF z8W!|mhOR%?7=itUG%b7HleM|BL%VGo+}RVAC)a#$vO$&Z(&NSSy_OBc$ z>rP!y`j9SGuzYhDQ*&Y9I1m2yq7eB42>ew`JxeM3pUfFh#lD)o<7jR>gas0eB}{c7 z8ZL3gV;|3TUnLo&qW`l+M@2*NatfriX0JM_9c9m--fQ}kDI`a4iGWl!i-IKbRCUz9 z{Hsgh?q>MQ~F7%PJfn7^{!bSjTNbk++&p1&rj%}^J!HT z19>8MRMi%#Rdb!28=S}ruHu(!zVnP#;eH)^G!b2N!?a7&#bmJgT9nP&s?MfGhEFh! zseCMDsO~w|Ho$fo9$f&?rjtWtyEAef8UJQo_i@o|!f(SdKGDNWE^Cx!2GwIC(qAM&}^4i(3?ogR7%N?NJu0qi@|~W#fElTvt^% z<6)!UQpy~b===MM-g%ume_-QE*6fXTCdTk{o1_XC`ZICZ8sY|r5|WGt&N8q3yjU3_ zkKi0#eeUt#I$T*aITBk8irA>|q%jpW1~vYsV5e?6ITsHvk-T_sie`Abwv5t67Ck`; zNnV+WvQ+&u(-!IMF$SOELG-Ze;Z4+mmPYz8?M_V;x3$7a3+dMN^?oMrkWkqg=TX!S zd2KInyHNPV;yh51SY&}T=b|eK>4CWC>Aldo3ui_9Y_Av)bE_2WxsIsY3r4n(N&HiQSc`(?dC@k98&f0 zoA&kEXIX3rI<};WOy>QV(F^tNZ^`Y?2iu>$0-_kgqy7^aL<2geOf*rN@+c@B?T()& z-WrYAYf4608^(=UtoQjwD_h~4848J_-uKqWs1eKm+;nlL3CY5Nv(<9wmChI5^?ih4 zSO-g{JgNGmbDbGMyKC+2ohj!VG9);D+hu)sNA-l8qjj4K%cJcK&fh>NQ`yb!*QT57A#MMSp6bbY58Nm}e1eW0Y>G(44kZ zvixn$OmZ)>1w{V#!}?{Jp3zd?^0YG0*vmn&DZc|$nhuMFx*DKsQ1MMl1)e{f@qxwxV*xAu zk40RSxOu%WBaDf-!}S<~SH$;buKM(cUekXaQT~CioA2_Xm zhOyju-X%EFd-+iGy+{8y(6B1nqn=@JjX_N)`8IdSZ;XE{X}E1aQ_(#{7IS{WS1uwi zfBcDRcJA=7O@o+tq%1OkX?Rx(Bs=Ynf_@3Of;T>a0f~adAOBKgxjN!(*zZ!h@}hBGZ<*dPV2t3mh6}c` zb(lUxQuRbcQkNu1Da(}~zY(cFF;jPtP*vhuTZ{iJ7(X%b=w`!)dXH^djNAS?NP%LG5)Dqwj^{;Hx77{z^IyWVnIbW~pN!S^a zB;t-O%mNAqR?nnokZf-aL>07uI7Vwz+`AiUEmLB z{@!Vj9HMqQbv|3muajsVxUIP~yDb>n~CYShE0 zX<=)J)08xnV4Qw>=PXQoF}{T~$$D?M`VMw0fS*BmxB}AA?-m8*8t~p_IGMV(75;05 zRp7JDXqq><$5j}dbx>9m;7y+a3x%x}t#IY46`~j=ibSena^X4kBqKyq*U3y3l`QRN zqJw>pHsr@@!tHz2PDjNaVx2oaTij+gk~DMiu|I88PP|u`mTknq-qR-K(+H|bk@Ox0 z8HY7Tm6sL4AF=seEh(p;gSC4;Aq)+Pxcp5EheWGb4x2mp{@KXWK&)XAx*8alip64u z#%1%Vyy6Fa%9|U5FvuWME2KX3E#Y9txb2Qfn1Q};ve>FLeJKXNB%qV&M)u?Q#u(~R zoYq9bUxZx_yQ24{&&BQDseBa5vxhc-WsiwLF1#s6%>02gSwI$&65fC0m*pp$6 zUuncVvv-YmH;m>=Fi}xL2i?+va71bFh1gL!-!JRaGzQlb&64mQ3o}^pWYlOaA8R5g zeD)+{j=x&$-%x*=$_*qw@I|Z!a4?S`2f^ArzH3o|I%EyC_K9I@&yZ;}Wl&s5!^3Iv zkL0zB@HDBsSv1QD@^{+UdBn(-P@DGF?LDy!OTNYTBMSjDwIj|iT7`g5o>XUNcwn!U z{xBcE!#A|vvU3J#+tvHUwL!LX$Mg>>5A$5bpyr?+GbL!-==k&#ynn*q{Y9pikyrc{ z;qJ}JbH+7YdV$D^yQcur)#jFp)zY-Tz>1*7y_X~twhW&Rk>z&51ztlJa>GiIELgi- zjYhu9EU!$KnnxAsO?T(y@W&0NfDDE#=pU}Ci00)i#eYsoL`-88N1$d43P)T7;-hyu z-~~lY4f_+vGfxy3vU1a&_HmfxeLqnS)IF-ma0Z>6Zmpn zVY+k0*5?MV*Kt>QTFpX006Wf|`;aYA?X6}YzhYVQST{>##MomyM#1WOOa6!V1tiVy zLSY{!f?w0gMG#mOSa1F;Q1RULXmbn4Op465T~EO^RzB=tQyw@SJr8ou;k>Qb3}QBH zJ0XliKNXmU`JHU3Lr;_18R22itc18N13OMH!(3kk=rNWBxiwgb7jF5UQP=>JnJI1_ zz!C>joyAmTUVcDhk13yaE!Eth=;2zdjHZztIo!5f`Li%Y3D*sOllt#O?YVxCfGl(6 zEx3%PC^Ie`Ms%_TxWWU^HVn=tSCb3?$7MtOxltgpQipo0|GGleTK{j?N7XFjc)RyGWXV>M<9Xy)U^HgN6_M2IevuE$5;_4AGZKj_?V{ba-nA+OVY* z#k#K84UBi6zn5Os8~i112Pl&FKP854dp{Bw^%L#sDg>HRe#wS2EId|KfV>%bh7j-maVCN`NOmj4c+;*7!-u9A(&{G0Lcw z*zX3Oblqm{NZ)Rc+X>_5)-St_RDSl$at6(-@Yu0Lki&6;dy1Y{C{^Gp{WIP2R&k_w z=eZ)`^MlWQXpQ?CHA~Rz$+Nbr7;Qi`k8k@qR5Gki)Q$S9WZYQ1gxcpPbF5b@{k_Q+ zAHLdjBmH_oMMra$^0|+V{utJzbXlcHU)u)Oyo*v#4V*0(TCyqAywbiC=*osQ3M?vH z@h2~14aCAbnv?)rY@o5v93T7V#WyP_Fhfr zNMpz+wJqiieH4QFXSZ<6aV{uiwVR1`p_qhdZpiC2kmK79g9fgfJFyaDk%tGLHo_5-_ncRm?BkQJTxq zV<9t41Cwx<7K4l)**+%1|wATv$}6E4-mh})`4p<%ZC7-n z?d0&=Gws`{@iT8%p2j{l4u_a*m%iEEN&s?sCi*lL{r!`eQXqco%_#*dZGc0?*X&Qc zGBP8wt0WjpW#iwVnt9_!lu`G|g}7%bPH}=vk9do!d71Jgm$APWI>zh_3%^9%k2q5%h2#9E=v>bAI5|EwvcL6lnrlc< zP}dF){&N#2ihPW`IxC&7{Bmv{e&tyYTq0V8!9~U)3@Mgf8?8?R>i6qmTw6ghrk7EB zZcaYtJfx1`612~+E)Jw_<_fgS|aP$mY6VF;FJKgo#bV;hG7$A2MW4fx$r-?^nxrx?0CW5>am ze1q?8b*ohb4ffj&!vx+A6PnylX$5vn9#O*t?U{lZ56o(*{M^H@cJh(zJKKI`7bQ;Bqy>WOPtT=hB&H$5;@fuc+Dp>S_K;z1)V0$TI%8v3ujW z?MhGsG|=Zmazn3EiZnSy^$Qv68CD!z-!*$UfgJFlT7#HOo-ucpp)wTkhseQ$G+GJap#tQ*bV)qepI4-Y|t&} zvP5$8%U?A)Cu=-(2`n%Ng!M%}%_$Yo!{cE6mqYev6!$YwMdH=8taj`7vi#i(a!N^< zqMKQTmUC|!BI;-lq3B-N*jv$c$PvITMMWeOfGJlL3zlIFsiz?? zC}&7)MVu~l>jr%Bgc@@%a9`5;ixFLj8_B0QOJAJ!0WU(IGpBoz4Du$yd2J5Db~5e)d$}r z1)bM-46Cm{A^9HihlwG&sai?qFA^Op?SBf#d)=r;sam2z!-aSXLb4SdpR6h~yQZ#= zsV81ar}5}|YXD^DgO3dP&GOYgVmAYhE+hk-boqya^>q2QgKjnby8Ygdr}k|Vs=Q_B zysZY9vzxw(lJs*iU-^;4HmIsJM5O4f1Hhecu`iVVE`u$BnW#t7C6YTT)1A z$`1cgFe4Ny$5t|%d{dymNX4r>M=@DYOpkHAva^C$Dns%xP{m< zTr8iaXs`l$Ck{55np>@b4o2x3$6ZEERkQBut?pU>&@$!#hF-fQf85I{5B8F_p#jqy zzjFxxaYx`swpGx+-$^2xJ^sovJxw)@4CDh_$L-nrJ((y<=YF4W*QCLTh^W?Xwp@6# z%l*Z9X`*g9E^hs&FxY6ztT}EP)>KE-3Ju^k-WFbzeRS8J9FHJR<(14(@b0pPE-5J4 zkeLx{<}*MHrI=H(p2U!VfCsrxe_ISuGeof=w4b4t#G0UiCmDq_Bn1;yxhHIkY+pu> zc>!B&S4IK{85vl*`;l3}lF1sOP~Xy)k@_%qt#^kEJN&SQ!gE7dQ~JoypzmUVAkzsHaL?=CuP2Rr`16_W zSzUIP$Nx@wj=cF5@FBmMGOPU=q#!6Q7cUHpptF~uu#l3mkMz^OA~^Zyw?P8MX>bB; z4Hn{V$S*Y=c=zXowJRT=E&D>(&~_^b`rk%zBndVUTnVoCt^urxgpT;{FxLD33nM{D zJzhimzfI{bfne>mH`Tu}xIzBq7}szg7joA^c&-bt1S3@@r6d~Avn<5^sh)-d0Zf!5 z2T+B-AL^^$zp2^jN%~7SLy>rRC9f;wqKIVV?E9NH)%X{dLZ)&##d34LWlBVN-y$6X zcCE^v<%9h=_hIzS-068-EWCXo>M|5A>^&-BUX}rC{kQLc^ml(@vw9#PKC!(pBO<3TGG3wL=-KS3ct$erX7siY$zE`}^oi=HNv zbP((8^#fAsn-f|iI8sq&1 zUpWl6+{sT%)%J-$KPo5ySEpW9Ka2(o_~#!V%s@yhn3fiCDmOLMZa!!A)Q9E(-U2F& z?#MzuTxM(KiyR5Fe13y%{<>RkvhAVthcS2)rj=iY1L%o2cp z9N?QIse#!E5D=-RW2Nv~5g()$2;{bcBUuwN3)w2v#D+%PsTf!ec%6lp-KqLOZ4NX5 zg(M?A=?g56zVP`gaI`;ChJoVuNb!CL%mmhknHd56)leUGVYj|&|w&WEqS zzA835`?ubgSKb!2OXp5N@u!{?6?0 zeu}mS4ctL`rT=DE-SPLqU6$VL;8!<|GhLhpdL~5aDvZ+b zT#=1`C#dNjy9w>+d?V?>?oNU5oxh4in(tCbzA^We3BN51z<}=IdeQ{7y&FxdvHwZ> zAef64xp}}|+}}r?C&ZV_wqIZC6FwG;lj*8J#rx(cwF@$&nlpZkUkR_PliPo*IOFSO ztSNQ0!teNLxc;g0E)c1yU@dl?pkCJ` zJcvjL!Nc$4^?!XP!*dhI4uRmtNi^rWR(#HH(=Ebj4#!mg;j@sZcH`4uM z)KYbCKOYI~K91L-4K^2xuEw7vDH^B|THXtn#&|YZ<<{a7RjLr|;4h6xJ{y0)RacJS z1DLQn2a5()fkXsque5ex&~D++GyKRw?41A^9NUn+Dt{_AxRnAL2BC%<;))R;wJ1ax zA{h53Kskkhmc>t|SQ&OVYeK-(XZ2bNQYs5HEx2#4VwDs>dBtFNQCZddCqe{bxLZX= z@$G6f+Bz}R;D$&`Jnf!Z3bodt(K`g9bVeToPFDT#%W{1@SIS-EzZDbznNhF3-#HC? zePDDsnGM*O^7K3%4mg;^#(Rcqb)RPFsp~SakJ=8BiW+C;yA(=l{*imzu)=UUL;X1205Qy)*g0qPQ+D6y>Mm$eMp(mvhTcr_bd= z=F93OJ+oOKgMk?YDIdLNt`}Xp@ecfFTGcbFL zTYEB}d*bv5=ACeg$?f@A_iM_^x#CxnzVU1HJC7bBaNRvk6pj!)9Xs0t*V(|p{Coh9 z^*lJ2Nsm+zYw2Vu)mR>dc&wx>0%-Lh&pT+z-33l#*LEJ?sn<^C!-`El)!o_@HUfy9 z8gYDy%U-a~fr63P+Lz&7>Xj4X*TS4fqMIleNK?-?8f-%1@)!6iB{6;HVMWG-(Zz}F z*rC~?%_v9Ob}ZB#!+H^3p{dLngKVAJSk7C6cb?`fK$CAj8%f6U65{UXS&@@B z*7+^Wr?0Z0BKZf6DU;C=5k;iTDGiA*o0+ubE(X#Jt66I<)&h39ZTWCoI4lJn!2(C! ztu6#$BzJOG+B|}^Ja&b>zIK};FvL2v{gczP`7=4Id^iaFDa%-s`EPk;jyq;Uwl%Do zw!+KSs)@Y7&VKJx@1D04+}~)Aid`YhKye|r;`%4QEq}y zny^Yb--=r5w`_#0ch}75^u-f{@(#Z{RgmH#_hO>R=^yfPqqe%&G#r%#M8vKGG6aHf zZFsV^L)p&jC}^^`l`pAIww&--Z5OC_{C-3NeGyYDMP$fuv_cYb;v;4KqDI z$cSNI+jxO6JvHTJaH1T19T`A{rm-TkQjt(fOk>SpvS*z}H^It_cM+CT@sY@~^KUa@ zAx#{VEidG=iIG%N-|jV&TZ=kmk~<6K@%o5xlu`aOQ{xH>-Yws8Z3dr2ua}+06gGv~ zm~XiW{nMDpE&cLH!0Fks5RlXrp?24X8RLHqLIAX!t-x=6eZ&0 zCmpnRT!|@F3xVt4!nfh4#BCtwX@+oT^v;`H6k=N4z9s5jGhoj!B&+*!60mT?D%{pw zo)p1SJL^MGQ*e|o+wsWYg{HLn|MfwED86C!HFYCg@E(Zeio;!q0~doV#Ak{v zYR?7_Hb+|T=?`E&n4<@IZGYZ)sw)+hcoO={)nTmmxe)yBaY3JgqfwS>*lw*xDi(2}`hUTLz z^4U%_R8k~^3A&%Yk?Mb|Am^opsC&{k#612Bx7O6Fi=ysU&4!PXj({eWiX#z8>(T29 zc9H#b+!2*O6z+T4=1C#gbu>YV@N-)06-)lY3p|C? z`Y5V#kvS1?tsU_Q3!}J&$Q+HpBa4c=^(AwV?RYa5knP3D2^OX8_UeDu9ONZQ#Mn#5!_AZJU1c}P^qbx3_g1NpL(dym zi=1#5&t=y09o!r#2HOAjPGE3m<*9i)>~d8QFTs4+W<_Y3hNay;pDx?#?#_#JJbf8g zwSN&XFumC;`NU1gcuwK{{M0{Iv*eMFW0;qu&E3971uwMLIm1*4JhfFP1RmbHrZ7V- zaxxvNI{mCGDuxk&km21E`~{GNS4o(3VyPP{qq*-xU@Kw1bVpu-*t^#swz#Ablu!+W zWUxNwgWUQ8RAv@3;7|R&q^;U9$m;bA*qMhGz>ZYf#a1cHJ)ZI0V7|X@4(IzQ9ew~} z9^%B_oI-qEzed}5N~4mv%t3iD7VnG`><&c`4;|k8>SKu_x^%N~EdDstPOQ{z%jL~Q z-aXu$W}H3D>Bz7tM@HFis8d-Z3Zl0K zTV}=?M@m4fc|YL_re1HH9V^89Y?PmAP9?P0=otC;1T5LTrxI{mJ3kpG zpfZ0sudC93TseS$Hlkd4OG5$nv9B(anNmsF49pgcFC&Gc1L;TX?Qu6dKr4Yyr3SSb za5Hd{390YO0|$c>(S4%;yPoGscG#}>6veQf5-av)o@H^;b-a5(7u=f0Z5LQII!6?= z>I1Rp$?8SS&rK#^4wB;L6VV-fgyP2`Mof>LZ1Cc1=JhLU^HY4b%SQeuXh)N8X5Ju? z=93vVdcsVf==trLMq7g-L2ua^qOXt2g-d*QT-cArzO)={-pO7Za}-{{-i96RP1hkS z>Or-(e}`|WK+Y-pWi?SyL#}g74p(m27<|~TZpZDqwI@A!ZY2xG*@Bpp6ZXx>)}%jc zAK(QQwj5hO66N(A>vL)>ZeyjH>W8$=N=Vf%-9d-mlE1u368yIg;0jV3HNTKIJUTI* zMpa4crHw`&AIn~aWX&@?>4&<%8AZX$OT3JgS$u^35y96G#2(Q`qWgUmB~44Fnb1>) zs>P7GijJvg9gvJ|Hof(k0+ij4EZmvW|0E2=kJPAMVXhVJB}*O!gZ{945(>xyaF|I* zWNUjT5;$|Jot1bj$1nYbUfhkV)g(lZ%gCG4(#DZ=X{o=;mbou-x!>vWB<`5e0@=?p zOMaS6AO^&-o5iUGc5vDcpC;~d*aN}_BMy_G(y7z-4r;Y!` zCE%<`>ObtRf4y-iIo(D1WX&RQcNS88?l$0f<3F_cOjjQw?52e}ADKOv)R5c2HKFgP$I%})a=v4=&x+{Vf(%e z6!y2ZN4j_QhO}t67J3TqAi)Y~PTk(jUe&ByNxC@oth4OJoW@;teAALY#0`YO#Krx- z&VxcKZNkOpe4I2Kmy9A;6bNw@uCQZ*R#07%=8aU%t%pWYD+#}MG`uZ) zSf;2?vDZ{_aPDryTrR5^!8H(%|RzAvyGT=zl|M=b^8nj z#(r*MaUvexPhq9%u`c+UYp_8EPWC7MYJTyI0-gbfN!(iV(AqWUKb!^$2gDWa!)-)A zM5)YyffmVE>iy6o_dhq4%H8+v#?cZ}YB@mu9{VtenaiQRJCk@H{FUp)9b17;q!7V= zWBW`(WWTSeu%-wKymtM~Z3xbJ`2DLJ=+}dX5Z69HIc3Gl{2f*8UrweD9=V6i0_|lqo5oQE>l=9z|-kvg%8G9`wTp^s|53?S7adFt;D-ajy@kyp(92 z6p;K-OYx~4;TB=p2^JFB_UYLr)9_xoO4Y3TDp7+ zOOwBTOgS+{A_dhRds#>+@eI+`M#8dc7Rpc7r5d~bI8;YcN%ptSXo%v*Aav^q_qFiO zZ5cOMgHr3lu;4sF25?htt4V033TAkn2Z<*GlN0 zW-{bCq-${RMtlOa>5M#N3ybiXMdu*Wa%gha zlAv_mMrG|@7G?GvVSSz2BiRYq%7*RSqp3s{QPlZ9jGi~G6(2TU<31*eZq#ABU!#^f zp7_P3Q#UqCUwL0S?(z3pNyE$P=lz%RZirc%O-$ZNyzk4y$D&`zuR8Pdg6?da6LHVN zc>|#M*3Idyw{1SsgdEGflqpd>+kY$EXw+zz5B1_CEvlAN_;{h2eWJ7=YLz}i!QC9R zVSifA-EMY-Xmspqk0l0%@VkugZ~!h^fq?$f!T352(ZZIDt3Dc1n$?22T~V)*eMDKM zrfLdPrN?oDw$hTXVqDuO?*HEUTgO?6{u@W$-1xr>wNm{@STrONIMJ9RZC~$wzUa|n zBXOop)&#fgOj+B9dq{RsFwIZ~ldQ=IqJ}39S=a?!yiCI!=x<)g9Jh9JftEz#^wOV} z+d{ALGmQiyOtRghhw@o#^88Z64#y^S8;2Ln>TLm$+WEfeqM^# zWx%xhR&t@ypy8-AwreXZb{9)usD;7+R$$5Bnjf5V@fOy?)L=P+{k1=9Sb_n^AI@#B?J;7Ul3;(4 z!+a<(9uS_oni?KRqr4^9C#hUO-}<3R8@Zt>h8k~_D=|i#l@Pn&qE9NGVUgIKt(HJY zUw!I2^OxL1@G#phA1zTqoB2pP11+U>?%D-Dn*$P>AZDsRjcNhUO)7jlSd`~b(}c$W zaV+>QYlp|{jGdo+XuprYp2*bdCt`-+NVDg7Kgx=?V}KyFmn!h~W9RX_&QriTdTrBN z_*>}7HO|5n=$a&}f+@=Em8?-FH>Zmba?(b)Il})4i`5#Q^nd7jr|`PMwOu&2Z8f%S z8;#MVX_6)lXKZI?#%S1h#zte?wrw@GcD|#3e|xR}y2jBse#bNJ_tw*aeen0oHW&vh zD5?)6<-JZxYj>**M%b%oY9Rz6U9 z_^Wi%S%^ z?Zz6ML2>`0E76>$IR=3Z4*h{qvc@*I&N`GP%t5HN2hE_b4F97iXQiQ%CAQIdr9d4w zUON@(o5@q)5;QdGX+_$N!8VDB&IlU>^R-(LQ-R0oZs=2ZU~8BN7|Cbkb}|oSujjh_ zrql824f*_`)SUU@?emWQ370xsI*{rN;`J(^Do_Nyiu(;E)?KArG!FjEW}|v@61Cdp^BnC?C?rsXc+ex7DY=1i!IaY<`S)AG`~u(!;_zS`%1;`!=Lrdw#5febq$L)%#o8|QeD;( zd+eZ_XRr)373hcIJy-}$!|K!tzSmGw6kD5m*adpr>3vlH1_n zg4pY++iL&{`Ci?jK~`nRV9LL1&j7o1GQUX)lZ;@;cz9w@-Z1Ujl#p4Qpd9PvUGh9m zLCt!_01q6!C4SQH-ZP#T@@wt*&E19fjyIBqlMlTdK^#g&_nQyR!UuCcxTh?FO$%j+ z9z9fz9TqVVM2>S8B#9|;3-6;7Rg~)`o`}1l2f?*3jR=W?C;_IR{9ZXEzz_^i5M08$ zKnF+ZUO7GDoN&c{P=3FLWn(&0elw@{ejy-=k8FOB8mo=%f{l_1a9du{xFVrFLU z%N+5G6S~-F{gOZ3T8@&H95hf4hW`oQN|2f;E)RvIuON(@A->vBi5)ac&YF+qLjt;SnES z-|PR7t&)CU9>Ze}|JGJC>)^k6RI zuXYe9rcr)VrbwERkP-Zhg1J=xWV#0>lqo0us%HHY7rN^BotP!*;2Y6e5705dP{@ee z;bpCTJ$weD4x`iwQiqWTM|h9s=qvM7s`s}82XX?0ee9@#K@w3cx*DJbD zGat0imIy>FLF&`9IZscIeTA^Lc60Ph$kbFZKuiA1kcY+NyT_R2R(1WH91+KchN6B#_2O|^^KjzYSI`XRo*Z>S zGjA%*Vq;#3JZ5Ts=Tg8lqSkcq1zDT`PYRiZ*m4%60Nd>!)JOGJdJH*j8EH}*lA^#0L#V*e9uOrO8h`fd*=X&)XXYnZWh}`(; zqmk!J#Y%)?c^7eW+cuIknyG`Da6Q$_)6fp*Ijd`w5;Z5eINR4`Gtu~ZBoF%C-l*sE zk1%_^^62Saq6a-ZFYJr3{}sd|MB_PplZK@SiH~dgR#Mvs{lnBYdlllaco<}^?Gzdp zfKHiw-Z5Ma_&ATTiGPiJEdFz17~lhrnv9AMGAWNf_4fGKtRy(ZZF83~>g_a^Ad=q# zJ+Z!Crrc&0s}dBiks%W&v>iyiwS{yPo?KU{@XDDz0; zc>fnS1#SPQ=OPuigHgKw_T6uZMzRazUr?aLvkd(>Su1MuMixdWWufcSR6D{n-DR|F zX1+Qm{)u_$^VR8f@A_mj$zWq6P2qqR_Y}vp_#3ka6T#hgp|*4{m+BCe)BsV}q#IP* zgWS9gsUl2Gkr6RGX|w1VhFq@V;uLIr1-xcu__USc183HO78pB|!!LHW7`aQ=Giz3n z`3Z2v#$wup`;035#3|w%u|^XKFHylSEP+k-{MJZ@#Vl)lIFWUyiU?sApkhb`2pj2O zPP?rIFqU?4Eh?bDK{%k-HpGBvhq3Go^o_s#xYG1|weeu&?Hd&_a{{B)n~mM!wJT@8 z^e2=ytdB$OkPCxZ1gE$Z`@BaBw$pHu-f;+t+~}5S6He|DbLD}BW$KhGDm!l^`YjiNq-;uPMd?9FQTO%ot7PY@CJT?^ zl=_^BvIa^2sV_r`F%>HrPQ7f+`Z=RDRkW)!?06^_o|al*aYr6(SMmXiqf}JhFpNuS z`ji^R??krm8qppw>zpHA1z2+<_1Kw)^4uxg+>Ed*q`$UJ7=s2$T1>L}nm!b>+MK4T zADj=@&WECr3BiDM3S8+@{T6s-2m=Z39Nc{1h>Vm)NY(x5-VBN=n1aNqpFK)ZAChVO zB`u^!CkZLJtW8Siay+@7R0bHWMSfyY%F;x>qyg1M)Ik8iwp$gME1}h+ltAWs(pzq7V!Hmg4?0m<+&lXm(2(Hv2Rk)2Q=S8YD9Uxp`klA3fecMng& zh?gL9sqf(zv)xk&*HzcngSX1f&$Xg)&kQF8!3A0_!CXLij6|78sgEF%**McM6WYHU zfQFA!l=w-E?27A zdd4lkKhXS=nbIeL4r{6b(_(SD6Gy+6bs!iJ# z2ZFCc&SC2Bwlpg${vuI=r5XMnkvfQ2jTEjl z&okiuG93a+IigJ?^#gGqN{uIe1{m_KV4nqm$*aDhb!fcX8UKu?^G`V3jnUq3IaMuN zGi4?tqi#8vu#Jck4_89E3pR*PO2H%|9NZWdN znjq3Kh+>=!{(y>Tm>l1MBN^r*NZ=d_+&z^7C!i>umaN}SZV!c+xh;_>llE7ZXz}Y) zRm~Kqth)HZfjCzMZ}i1fqL#pkmbrM{{Pfq|hlQfNBdoIaP&l3yvzV6kpDSZ;dS)TF zzg-leu^QnGLRY!;-BOwo;twgW09-@A(Xrt}6(DcJ<0Wqab;Ck7J2{_P@h~ zAvaYz#tD0FI|bA(&*e~F@e+fE$PZs$luRHV(O>1?%QPkeAhNe^--@@=AN8MZ*_a+D z9pE4TnZaa#Aos;?R$OMq28(&ooq3+XY`Fg5{?pSu0l+`aSl1wB~qp{av^Z*>VYo~4233dx%^if8)aB5^gqvhwtPB-&XKTIw~Z zCmjSmZa}`^H1FX3Q&j!;Nc!n}Y6O5CK76{cZ@(MAs_fwz$i$23FdFvqps_#htqzt;XLkScm&Lr2AHXh zEdV1${WVPwIMj)COHW0+$C5$|MRVPY!x@Bmjy3v=h_W^SuA8{ydu8HC8+5P5&n=2W z6MFJIVKB9Ogz{{>5CWuA(Y0nv-C1>%XcK6rbLlf&!+B)g;_(EO%Yy(s$ zWNVai$f`R8bUG0NyF#rfQ_d(?p5dr@BIFx$GsByW>Ql(Zkv1FMCxKKjciOx5zgG0C z4R8KWTKaSi1AxNcKzW~~OHvbc>sm6zA!&WiUzyDg5Fj{>&EMsp z*Vl5!t{u4@oVr(!j^fpJmNxURL){P%X_=ZWEW?SSpSQP}Keb?#$!~^_Z$~R`v75{ib57Ekz(O&O*v|fj-7i*s}-$ z-jIfHiuNR6@!OwuhX>X%Ront#wn#{*jmNluvUgOn2z%EM{r$fVCeNDr0p(lBFmk^` zi8WS!3we3i4N8M?M|k=O_?VD2O>y%taPhLmK0w2W+TSdoh%hACg^ zBxjCf{}kDTi3~2|FZ*djJX*ba>^cm4L^nLRPmUcCHFl##SGdA7s28$Hlh38FgfwLJ zw7bdT(G6`T%F%eP!ELC%y}fh%kA+rbbvl_g`OrG97F=(l+?NS$U$)5|AIZ?f$a=&M zE-^s<2yK zj^pmYG&yls)AZmBq+jb4kOmxvbfaE9dnJOxcaBi8V=^`BKf(rd36oB^rd`lTuEi}@s*vm6`6 za7N+3KgHtl%ge|Y`(Kp%5bH86pX7&Zq*VXI>@Ai@ zueRJDJ5 z;YHZ<7I2$92>CM$=H=NPYwbZs_$7U7g4F_n&6`MWop}5ApXxvOTbrlvz`?%%<2 z^%Bi!AdS=HG!fvN;~KPCX)QqeSlTp5C5e(z&Va{RF_fTX;#H7z6FdWhpL?m?#Ec0Cr z1Oz?XZK9c=45iT>1STA;5vlzHgY6IrUZZ{yDHVVzkL`+{U#89-ZeMu)NM#KzZwrZC zW$Im-8Zk~&xM)|(Kvgo_Bvjyz0o`XZOlf5P8Q9c)EkdP1lqdM0^ z+}Mw61g!2Wd_@o~kQ4zwz_c&WEqD<{PZ{?phWR^CGc@*+P5N-8S$Nlk_A*vk102+) zG1m0CG5W6i_qoNC)R3K`;HbRNHR&vc-3;C}Pn(#S+FOQyF{4SoIrpv&iF>L0pUcNY zcgw1;Uqs~8I-F+QI{|T9*gc*~2=6}?DP)He(;vQNQ2#@Gr@8oWXnXTR6YDXBQ-tb9 zcYMUBe5C+=h}eF3^54f#S;XQi1q2W5{8LS)edxKrSUw#SOWUJ7MwKZ#L3 zwm(pWB0=#Rdt44?)jY!C0tf8Ft>kHck=r|Jh5#cODm;R9<|kMlD9!E zk+zn4eJTEF*@|FRd|$}mMnuF_G#@sCYh}S%bW4WHIBR##Rgi$mMs`-;D_tZWesM_j zl_s)z_9MHU53+iX|5Uh>jf!*A+f`S06VzhJGK`n(x@2egNN@?cgtGOgziSH;k01sz zw0A63U#XXq1Pi5_-T3=bz5CAv6oj^=>H#?0F5c_t^?_uI(PYE*6DjLE{dw26FI-=C z(jPY>%Tw;O=D)3<7tl_Ys=gR8vc#f@;#ADhbAlnF4X`|e76LhRirVkDc9An&AT zs$kuEp-Rv{?dhkb*|-l3L?*%OI(?YZJ2xv=CjR{x;ZlS5|XeH>pG8- zQJHg{dY_;SN`deurFd+QvyXA#(aZQ3HOr~heb3LLQo@H)Zt_gX*p8IgZmS*A1sSGi z+rNR(&?n~%_e=#HWoM4JsxXlq{dDIj=FHK_dZ2{aBR$5q`%brWUqEjvtP`v{=8P^x zwD>3apG~xS1e&56{9)}|^4|nnBj0Vv-jl3~nuesh9Yd#nVT`rT*pn-FS3&@FY-cOb zQk?@<0t4p%dzyH^*Fwoz`dN&{_Ibt}`7{F&Yl>9>#)6CWv~}!WTq6<*uJlpM4CwF10A0=g^uJU>v2@3|0t28qzx< zK>cN-*L_^W{o#$O$cAI>Jh)&?QhCIx^v(-k*=dA;HVYvsdGC+L+bz9w|;07f!*M8X1)Zk|Oj<8|-?XLbHBMgKTqFv^=Ip+*Bw(l2%B)?!Bn+!q$ zJs5{Hkt*abzC}D_y7}@AWV-3{sS=U5!GuG^({ZW^xp!l2%>&boa}8a3*y*J1wE~S8 z*(m;xDn6o7C5;{cv%|nCS;Wf}g+xpx*=u?sUQrn?Y5Dx6)+@2?YL`RZ@(8huOfdq6 zEMugY-=wiH)BO1kCT0)?!vFOc8CL(=F{5l3J{XjIhC4#UWNp-i;GeeTbBU2P)X{KzO`3i)Sb9sWitRz zm!*1^674K3lXQ$x3vyO?HKo$()V|)5+6j-sYAqH8$}+p?eE$mlwGA$Ap;=BO{|=nS z7(=z~?GoWtnddM`-&_lD0nzw#c`JO^fP!SXic|NJ0dSD?=D-8{osLv|wUE_sKi_c_ zhq@hkOC62BG}#fB212BuWn9SF>*+)RCe)GUT`ylxA($^Dw%URMZafs4a(q$Gg14Y@ zIkx&qPyYm{37yiga)eDd27-5<`)IRd5PUn~*GsGUvlnV=T~h@4IV*p5YZg};Y3LJ( zpY`V;l;M#`QhIS9++c`M+^*=WwUHnn`k1zy)^3OkRGY46!Na0$s^YVCQA1@--)L#qW)jC~N?%D^&(qbX{1CqO@ab*bi2jU8;L!XeCv5_v3K zTF8uP#}_Ihk2znHJacUJp*^586G&Ior)TGgBab|t93*RDX1h84;mIILP_}oI>d@B~ zeQ1D0EFA)%p%*bp$_-v&^#3T=o|Zs7-CA-Oe75l0&1(Bi=ky`U2-_}|)KA`O$(EIP z@-T++|DI9rYR^0p_=!uVORH-{2%jc8(eKcDOrqg6mxpDQ z4miXy7I!QVyRco@+gA$%x`+7rr!iHQ>){Uc446-XkA4FME-#Jsga>Rz)vs5E8Xfs_zcO^ za6*%X701Zb{TqBAY>cERhjfUvTy0PV2R%*AYHjJkbNbE+UsMLbny!KsIX_bRE6*&- zzR;wnCD?z2j45fzU#~#AS|>#+i6*<#)?e8L!4iM?>z@QJ#(~rc5>gK@A#eme%Skem zQZ2m4tM5l}593JUm!i1LSo-%#Afb(mhZ01^XqQd8er7%_%|r_TCFNJq-xzJmNWmJO z6aza0W4oojs~R)&M%9+L1Y!{%&bCul7mHy{h)qN^Kf$%A*|e>D08b|r^Uquv%d!v^~+VK*EtbyJsr9PWIP{ zCOhkMkySAt3VRz61b5Jd7|>)VoipFcAkb!ANETF(8G;9KvVPg92X`FhWKb2$uGw#;V(m^i*%CCvfBLE+wKQzvj;3oF5AI|#e3KT`1r9=ML07Y1&75?{|bowJ9 z^$@-IjC6ZCt^*=vk$pmz{xg!3Caz^Pkqh(QG`2f6*|owuaU=H(d*mJa{cy&1g;kEY zFzJ+$AsQz53+B_VCxasWVZTj}HN#FO8Dir+u+(D7Nd)s?72;eSSa&i{?I*cgXsLV? zTzFW0-lBfLKWpm0+OrCPEZm1DJ54v(bnEIh2{|^j*9AP9!gu1MJ*Q70BVM(OU&btk z@UgX7J9H3kLE8yC&C+)ral4VJwyjnp-+E#-Sx-={|<3NRdjcRDX5SHH`h-lMH@5VDUUC;Yc$CXpU*otyYeFD5x-(5;TC z9S3aE`SUBk2U=0QLLp<$s5duWESL}y4IZnLlgROjqXX~0>-%7oI>U7^i4F-%PQE~@ z$Bf`-@xug9WaJs$aN=JNnc`^$Mno6^k0lRWSnfLD6q#a<0)PWv({OTNyXtNEWG(u8 ztHU=v(^p3az|fzAC{jZ)Q*y`E65pl+$tgOC2&0|zMjdzH21Zs;q zO&a?smL3&INdMm;03ir)-b1b^hS08IWI+J=o-;xl87no}2M{pD9)vvl!(F(;`Ci@1 zz{)h}HNyj7)7R6$rD3a;M8~!9@_YOMF}X>PH0$fq&*BZPw4@CT-Y6)S80fJzH1;c$itf&5JvH&0 zt-vge>W0DI3(&EDx#uGrIO6reor1)RrQ1Ome=7lYB5NsT%7cWGJajM?<2f^wcjcVj z2!+Y+(APjL*pIm_8QImUS*uMPIWQV0vWJY_69vnh##f>jexH$YdaygMA8vzhtHqoOA$01<;_p89`nt;-tV5hR- zyOVGhp?N>x+b9b(2#H zn0@0y)Olv$ZU5kA!hc>u)ZL+rRgQl6v3^DL{!2bP@?4(p4EdJY&34WbT9|lW9|9nF zUnv~=qIVXkpHs(jAEGAVAH|ohy-eJ*kg5mM^>bl;imR6_LS(O^=Cy1Jn<2I7&5Xk& zQsm^z6gFHw$H%;fs)BG)Ro_N(<0eL9cC-lR;ltoYG`UD59-59HrE>qxFWqZ7rgmJD zS?n78-B*HgR{E}5;*f$L!KGVUlJu~j+LItdqB&J zkD*7(#$&eE&s&oZ!XQHN$8kf54qE1qXBLDD61Wexo+lMX;ir=7cjS8V_cm=7X)8Mc z#5*8M(=olr3V(2+C&6lcbm@jmDlu%b`aG!IhUaADnUXv64n{d#EVP-*!({ygISK&Qa^ATR)I9vyO!7Ti5d}!TtINI?DE~w)& z=Y$24(UiC)DDa5EbSfdp1ZxDGaZP%Jo^ z@8}H&rqBt6D=ZU26rK#cBIzIXOw#N3oS9W6eBObUDPH}+;sd60(3X*`?w?5#C) zD`ks`-9=Q>iwhRcP?G4|K+Q?lB?%3bV&@-{gpXs07Wh3u@>#me#;gu*pTGw4HqL2G ztV%XZdS0M>ot`Fvp>o|is8E%4S{?gV7;5zU;MZ^573zsaQ|hyU-^<+l=QX=%w1?-% z;T+kd5SA+mbAH7XVlXis&`T(amp7E{s7o)9-R_cV@&NXGbMGIM{jzU)HGJ)mUUuWt zOL;XPjxAnfg3T%{g16pF-M^kpTA z^w@?!an(F5liYAQzHy2X-w>O1aNpSnbnN<>3^$tjF-}+}`4r74)$G-08_FmzD&ZT;Cjl-o!%z4js-EJ`>=2(e8X6tk@K-v4Q!{Z3xlZ3sJ!?=7jX! zRIN&<1~Ph3BMnd%>kh#i^B{}oJic>$pgztY#nno)=}I{H5a>^S{ukWh&zrGo66(qO z-OmN9wQ}|$^mZ@+peb*5GbgNg=7k=G?z~yi%Z+dYW1Gz8Gh+z=Rq0^&3UvY+tWby= z5MFS;MJSvYp2n$$V!vbLP9y(WiI;fZiE(FR4zsV>AiD77dzIW2lH4Q?ilu@o55q0Z z!fEqkGlTI@pgv&c3L!lrncabBW_u$|ONRiL@;P83&`m7^zv=+XSk5vr*zR*d~-NhblB z{Q=8AjE)2f6%TL2IRMr01g;cg+GP{`e zK9pN2d7PT@y*Jyir>Ca%fm8=c6Z5WiRi@;WC%3VxvRj`Fa{a=GmJW@ij&HqRy8}DI z-n9Ec^XjUL$X{v}P}wwf-qG4-D9f7gIuX45R7!n5ZLyC{uMU0t|A9*000>?`;dq0S z$qhW8QKzsGgke%Xq#lfveT;Zr6SE*5r9{$YAA7lDC--8f&QJF`QX~VrFY$x(sFdP+ z6tl2hlt-gE?vi$^^~1%sR#UHSp8x&fYA@Sq+pYgw7eh>WiJ^Rr=n$9-lS7Q1?^^zKX4{6?NKSHJZHG7bx2jZ?(;86@ zgMF~@GnM9K5Q|z+`pZ!9h4NNYH?!@<*6q? zjw|r`gC zh?{8Lo>>lzL$(0d4A|H(V@Z~X>D>mXdr55Fao6!(bb(~)=CS1yogZQG$UX9vJ%>cT zhpy2&Ho?lA!BQ^fMC2af5GfBz#0!BI@)_Y{sseaU?M8peJI#@BQL`D1>-{AtL8F{ZYVokXR~3xGW%zJkKBp7#d~AE z=tP>Bm|MsXXB#p4@|_e$PRoS*Ml6TTKoF1(2T1csu9 z=G%Qv zsA(g;*-a8Ak5w7XTU@`O`=TDP7aaaE16Lo0s$`a0O`+Q-w(!^slPkO44 z6jX0j^KRrX%zP&Aw_oI*Q&y+(GN!T;JXrJmP|eVgDZ;IJ~Tm&dVH_GPfQNQ zPr~{{{QI4Lhp8VL{#kZ?Q*diLw)a--LL+XE%eYJ@&+UX5!)uG z@$BsK`UFDkKv=QY7A1$Ow<)X?%%huHbk{iNrsqTBA5%sA?Ri^!s#PinP-QDln@ptC ztW(a-RO1HgcrtzN-$QjR>CQUW-qF}cEKPGAu(lKTG$RGJMyK__B{29hkNJ;y$|G6Q z3MFnV-~O`zJ~ZlG$dW$wW4;tW8>1<9OYr$X--^&{wNoeN!iF*$<1D2`CozaOtIV^; zyxrlYbnSFNFq*wM9(#PyFzE{DF8@VAK-XzqFL28d&5_8Nx2v78e$zuSm3>jCjOhE3 z%9^bC5oWqE*gic(K(gMZ%E?`b(Lv2B7mC`Ke_RreWbzb!*E8zGp+xDoo8y@$PuW13@o){k!B?6O z)|hYYuI;b)|FTegg=^h6f9>;qhb`35^p_s9pK*3cTSbqB-~T$mK@>**m+(5g8f#GP z1WhRZVHUG2aux4bx61S>tKuTW*s;05qsCMx?Lm4aEb_B*RMsFY3b;pUoTSD z24e6!OAQr|=rS?d8CytFe`#@Az2TTK3?q3r5W4;4dEg;L?1I+g12QOkGjB`1X>;>c z4#K}Cb4#x0$$nkolCTTN7qXwt9|5nrrro5=jYCqePQrE83(+{H(v0%cmE$7E@r&dP znf6wqF+yXhS(Kt0cEh*AA^pqTrfWnR=HuxfB z%(7kdQmlzLU^f?DTixuel>qqGZ9Ebj7*V=deGfQ+z7n}iZyV&V%<7i{f)3$Y+ldV2 zPBPZ)lWk5RS#Kmbihrt8ZFO!`3&Lylw{3DVdKFUkf$)2^MwfYVETpZD(4@=msju-< z2(}pyms1^=Syk?r*HgeYJ7|(;N&sKy=O(LOfrTPTyU+W2<4p(lO6cwZ9KfV4VQUvm zNIKuHb74dkLcDuBg|y%L*>l;;f%2)O#dp{6uM-u~?FiULvj`v7E`3?}(Yg@zN2L*FC2-%pWm}5?niZS_@jyo&wKO9qix!w4XD^b_K-PvXETLQ6E&Dv{znuS z%tlZDBKhrDXx8!(kL@s=zG^Sqj=Dxix$8Pm;4!QE@jF~)8-dgy-=pWH%k;r}TnCX& z3jp>%xa7`D(1L$RR)QrSc~t-YtLZ)L6fxz%4;bDGBl|D6KFU6}WITgEf6r7`?sBTW zcBhR`*s^}^R?5N`3r1tUsBGf?t7Y~zWgz)h_(kT(KT1rO`r>ObfVI2F;(MjLbO$3I zW!(yd)Hdf$c=efl6>ZeO%2;U4C&}D&KL4|lVb@WxjfWX|HOuPmzBbuoi^HH!@7;+( zxk;`YIvb!)0{b{Eg47|hwkaik3E#-@5bcUS%n{^IvOs4d@=Gdw1f3@i#gk7k`hH7d zUykqP>c`Tr44L+hlHWSWEb|U2ZlGrge&r&m0#6-_bm z7NeYu180enW+rMLe3V7vN`ig!vyS9?V{qc_naz($RV+=6FxHLZR=3(saPShdFrJOE zti(eCB6omaKZ(0IG!&Mrh$*G4To3SPDJ09DbyxmT4V$bUm1x*%QhG`*Y;n1Bi8{bk z6{8g7A3A}Zj7!dp*gRzt{F|y~C9M(^JaY<(ihxcLYGQ@bJ$>FfH>iSCyHV2!9kz`} zK{x4%TMGR%-{qifv|STO%c!pTkL5*y$Y*d^D162G?rIrEf(0hekw#oxK4s&qh z%*NZsC~?diPv$$7ebpQkZjzgrnLg)}#6E2MXc6r0xPyS_g?M%2A6&nHejM(+yqx)f zVDmXvE}q^>*ON-HjPT(vc=Ajg9W>2K62vxycz10oO1@dlD=I`Tz#)t3U#TH6N>}Yqb1T>u>~;blvfK!PCPBUNqzQu_7XKmW zeUL|pygyqgpiDi-^^;iJQ#B$@OFVKiG9IpB2h9ou&c`|ah{a(knF8>0K+Od^%s^>Xb2 z^Yw8T!Ts#A`b%Ova`7_a?70by$I!N8+wklJZyg#sn24~))l!u{NWgZH7mQS;Wj9cq zD_bhCQKJv3H%dFzENN&kg{moKk@)K;X>_H!D_6)yFjAxD;T})m%s?Jxoou{fm^{<> z8mIu z&FAi$87|4=yDnWI{Pc@NgDJM3lXa~ZwYf9Znak&dMXu5!Q{_dUWEg~E4jRYQ!n6Nb znaI|~U?YJ$5+4jx-n({UKJ+}Me@3rH6!{Y=ae~*EQ_HbKtuPWPS(#_L%ojF1W3Wil~hhZk4d9t#^?N_Ity*On3>jZ>Nn zlB=R5>tu@H?o>Yf&PiX7cnDuax02t5L2K1EgBM60Wv zHQmzk$LB1b?7P!PU?-iuUxJ?KtD{Sv9C&3(`n;$%47?f{xk-(s%!o~>4Lkuo>~|fr zWDpgE$yqP@+!~lC;jRl1w*Pobb}43SmfcDeROcA@G0?-hS`>#kb>NU8jDA#Z(fP7& z2gcUB9guXYvCk|-THA;Qtar!q|I6_5Y zq5*H|jR@WAEzM4196(uw`PMEN5$dE zD2R|#_{>Y*_qoA3F4bMMboT*M^#smDs5a&h`VF^$-EFN^(|u-=p44~wbt+~l-X1h)6k|?_ zK|)2~r{TiVZrU;Xdw#E}txFV-S^?bzy4WxC4POLM?O{mA1+}SfQhTde(u(Drr3gJx zrSh?>rUa)DX%z`MwLGGmc@}aUCm2YRgg&4rJzy|1w9;gag)>l^<_00pHJ&h@26Ub z5ZU|Ys4=82U~i<>&r(9A)PxZzIX^3AwB;6g0|TJE4u-6PpO01$IA4B&eEc_?BhfA4 zo;8%hW>SF$YCP{lFXG9#XVt>b5iXAxP-@m;<_+ip!5AEpWzLg1Q>9=(OM2>1>mh3~ zaTluU1N`J$@2jsyCReGy9oi)Fvx7s&ZiFI@87^gFT)ry}_GZ^1+ONZ2hEL5otjk@F zv(fOAwiKJJkr*Vhe{AS>tzUSI%ntUHw4|^!%j}o`P$S$9T04TX<;gpUF&v1m9{Fe) zQ`sar%#N5HzY5HbsaQ0(4N{zhvQ1_YL9Bjq^)&)5TAD&eV(D3R<-*>(0i4$k&8E;P zYX$D>+WeZ@g}!Dga^6r)AmzusSlJU zku4@$#Ej8(A6>6rSFTp*cB=ws%Ez9*Ou1KzHQ2JL-*ad~N-!|fzoa8$4pb!H0EtGn z|Ja0Nv6RbtKun>7dEApG5`3M7={iJ$?@9 z828OXPx_A?j?7Ozu@9F&n-c~yU0ym4;7PBdaPC~*`(UVDsNLNIB#KeCc*B(v|FkA; zb);Ie?=JQG$u`}w?k?CB8ekQH#vc}vw)GhsS zh*2dHYAIM=VjGu=c7dAKQOC2CSlW^VOjxmdNKDj`KMn9RUzkBoUuZABeD~aVK}1)> zC+o_g3sLldV+6w0*lI|t<8ndV_Od2s;ZfM*w^LV3tfaiKz`|E+(W^&b{l3{Qe$qxZH>@JT~;`R3Hf^mCTZV!f?`9lHZfA5I5q%_mF zBT1aP1;!**%)k;7`Kx_b*llcB_aR$Ju3@l2wJ}|SDF-9_c-qBew2d-(|Ni|jA8zVKerm)^7$FZR5C4o2_x(AXyY6UOc>> z9LV}R=8yJ#E!iQ3APm}zugr{~`Cs?Op4tQ{jv9fGegy2KzZD!;dXs}@ zCq{wJrASyy9wJDG_|(jOH|EX}P1+qE*CSkd;YmA=DNo2(rRrZ}Vk~q}U>!F*qml>s z0)qM07KmeCXio?WH*h9DOxh#2Ok}E%`DzO=*4kgtS56NQ+uuPYHqJtB0fPZpuXcL( z3yn<0ok^d21E1Xa;*4ReE@1vHcP(8Y z)skbxP*E>Aw`F8Q)!&&ZKL?6-Q>ZO=NF6;Jtp)HlKM09D36^GAb?FLI-U4CgDrA)U zklLCIYpwLrkpkYPkRms5av>l~O^K_?OTRmID9V(p&y(H0Qhis81E5j%^sBpiiR02h zYU3i*^?(_Bf@iqNE7MzcpwZef4t#0WB;9qD4qCnM>xo1=EQ&l$^6ZO5N~$v_MI0^6 zF5pr<4_O#SdcIZbNJ+rrby&4tUfQLvpn*c2wDk@n=N5X!W~9djkbj=#D-ox!AK1pI z7Ob{w4`yVd(Xj`!zf{PH%sqH`FbkiHp^QIY_+W0C_~C4M`>pkg9Tx7-gF#-XQ-*MD z(#*~uJ`9B0k&D|&4@j?JbS=JgJ?&u6m37KTsJpR6G%ron#){KBc4qGHm{7>@OX8i_ zUO|ChYNBpHW-S~C`XK=Bd?7IY-|ZX6mv+Z=weAs0(h;9yUsLb4xKjGS@zVknfZ>>~ z3}OKS7>C9DiYQg3Mkf3G+oA8K%%{%ZP)jE%^cGs3!sp^#U#aka+WV@gHiPY7N^y56 zPH``80SdIx;#%B`ySo&3Xemz6;$GZ>OQA@ixCRdaf&_zlpz?BBN8x0ei0cWnFz5mHx%{T&%V(kXQfgdqN(gkM& zSp@n+r54~AUX2`ai#^#>O&?O_)ilS*fam}5blmsjDJit)FhE)_VYxmbBO>ojVGpg& zJKX2|Y_*^qne^O1#w6gf4ur;PO)~e)QIWcE+Cw% z+4TZLgYDW-Pu0&NrdLWk_a{5pA2#>6Zz^0}++hO}n++JKVny7!;bEScWj{{I&Y2i; zcGoqE;s@uA+1wbt2U`@rJKVM`;2t{)A37YT=Gl&smx10k1r#sT)SSc`28nLr^hrBI z*u~ec-t?_OF}&BDo*;Er;LjjpguRV%_X(-AEF$zc>fE&!b+RrHU*CWF9j+-~af3mG zfJ>5Rh7YXwD-e8%p)x_Z;E?FwKq-q&RK~@z>5BgRb#N9f+rhb~|E_nJH#%>lAbAil z2;fSUCMgpcg#7su$S%8R013+arx&_q2=Z&}4oq|sfI4Spn)$T9_SZp9%yGa$PU3!< z4QYjLdB_E8)+NrIur}AO1#VF9cmvcksbw_MUraHL!;1p1TJ$y31A6tKKDz5sGB?+3 z=!9A6Bb~zkZT(>|KQLU+PJ4_QMOl?o`nAf00IVT2V8x&xJgAre9#3W&7oQkJMe%>Vl@wzoJww)_oL2m)lTLblB7IP| zX#50vi9&}z8-4X02FVQBp5nMUsPn&hSAM0IB;m_i@F*j-Ev-93hmb0A>71C=Pc~Kk zJ~gu9d%@RR?XB1hr80+-4epGh%~sH8MgDq2w}e+5Z8L}5{GFc>%U%dR)HS*MBUK|F zL+IFX+zjj$O|NDzub=aZ-l=PblGILj@f47jS6!i&q~Fa8*cxR1HM-OilBw?~rZKUYc46blp=HlzxUY2>_hyQ89aznf7?`k`zAxH_8=!Vd-tTZD zeS+)q(bo!b2Y!5c~*q0n`~8qj>Igmm4VRJl%C`8raw z?HT;p>Pdv3p{>R9+O$!@LG`QU&mG_%(RsVfeMe{`@FO5kx6w!;>6|xx&z>F=mD5k! zr&ImsWeBvh!WE>v6?w{KEUgi$Oy*FDDSw@dev z7<;}Oz}m6)ZG$$?8m2GLfSg|RVh;aLJ(HjuyKK4dhw><|Q06>hvyYX1Xt3BRoCGQoosLR0N~Dk$M%`a`p9KE z8n)Cwn_Zg%+f;zoGMPB-o}Qx>0;Z*q&&du0j9`;?{Gg29a~V*?9P!|2p@cS?#ZN0 z_c|`oEwNzRr9jg+CC?^^41H2JnAlk5CUEH*L&<0OlV0>LAi7{b7FzizPsoEq$nYPi zGR)QFWJBRW=6*O+URN)1``lZaWTTPX(5;g*sPc%pkc25A&&F7I?0XoB)P`~%yu?a~ z?k3;vIjV#|!PN=aXz;uZ64}rb!boT2stSub1PIug8C7wSOLSBEhgDEg6$<2A1__p^ zzVPM19~berrI84mUd3=7C-B@u!q0twy~Mp~Z;A%D3>|uqS*8T2{{o2y?PGA85s4tIJMr>U+m(D_A63qwZBLbntzKol;Ynk7aJ%2JNE* ztc&mMFsJa&H<`b`Dw2ZiMsCS%@~(UfCgSr5P9a+_t9>Oy%ltl;J1+!Ohg<3J^%X6? zFspEVkse2clupPHNbzM-ATR$thT+pkUU^xaQ1!h|TEcR25%6yW@~8%$Qa7D1kTtTU z^dMUIVDgqt_+VcN9UGj_cX+y2bJIJ@M8lZeKJyW5{56`GhZ=@`UR3Mg-52%9shim^ zKKnW&ImMx)V_-O?=GF1c)plD--33iRe^sK^k14h%rraNsO4#`M0kzQs@_mIpGMRwA z&30Za(&}}IYq}fSh$Af|e1LZ!Z?JDP`i6$y(7iYtik>qaIThueef>A`XL~t^u$H$C zgf}Ee6mp+iw|)u(YWi;0o!?&g!NT%-y(}WVZj6@5fAyUjzRpkxKK>ujj3?&)`VR>_ zSM6eCJ57GY%@#c=c*2CJcAiZ__`_NBiDrr`C>ief+fIilnFM}5$eY*9BT{78 z&X`-}T!fCBxzAykD(Zf)Oy*TQf9VLLE>PB^9?@78OAV$_J@kBUyTzs5{MJ^-~gGX9D*pfw<+qlJ?j1+4}{Swc|RTOok2{9 z(z&CbJ(E0V3H6KpZ-;VZb3?Ym_v@~?WEUT>3~+|tl7jnY0&SJ?>se7o6&t>^3}>(I ztW9Ib)8hGWy^OhC=b0X%InTH7MOhCSIN7NixZEjs1lP2zR{>;fsK9RDJira9Dx&W4 zdYO!(gM+MMZT`zw2UK`+scp{64%^gzvgZRyo}4A}tY@gXeV>(QV<<0;jvFBLgu-g6 zpW8DYjKad2cVsp-`PeYZ;}p>(^Kz?{eZ4iqL8I;%lXRNpq0a;J#J>zuK3CrLl0UpS zs|UP#NE-9fM}nOg{~KQOWIxl;`XET#TBcvo8Fu?pG(9F^Vaf^(r!Qd@@=4k0w|(hq*6DR1=?ZH4TdJ{m@jMRl{2 zrtg|5`+&QLfqPK3LjXBoX#VU9F7Y zH-52|Vu?y@w>pSQlP43l$gVspmP{F3Ykoep*FsWob94r#nfZunHj9GQ%j=C2?!T4I z1x)PmAQwAG4(Lkdv)4qwWlR}IqUk}EGtU67zKlvc#N}8%di5fgU$p|j%umKAL-}bF zYeSuuP{_X|SGN#_lyIIw$E?44yx}WT=F;ziPbmdkLCnIR6Ew$``+sogI0~r=+PVIq zv2XNXx?Mf#KoCWG?Zr9nxME*Tl3h70Pr@+DL}Nc}O2XydaA6T*ph>c&9A(f7Qz@s%ty2CGl5Y_elQUd0bpq51M7N}7=hry&Y zrJ{0x&AfoTdhHmN*BZ{ff8cWyWC`vApGuswSD-DhzLxzD0-}Jw5lAz6-2HvV`<(DW zj3f3!6a7T#53qz-TzRf{qOV#d0wUGgV@9}=4HFxTl4Pl=-6o+4y)%SCfVevN_uh(^ zTT+H?va6j>^i&>bsJIzUBu7`x5~ zDXwj8Ua0tvub|CEu`mfcK~%TC7gGAy5dt+M%{zOv<|9*E+GQoNDlweUex8PMul$H< z0vBMs`%Dtyge3G;bG_k_8v0>fIom(Lkx2UOWDr4`ViM11k=)TEpvSyqnQJ|>qj5B; ztjKfR#lGU8z#xj07gO)-TRod-3=~dYqZ+$e+!|i%*{eV*WAzGVQ?3#2z1M$SU?Ikf zKJ8JejyW)`r9pwqnjau{{KYuuTX-d#o~5~*+t+W6nD+RTK&Xk)7KU6F)RV|65N3f@ z#8j=XSM)c4JkMfzYd@jDW1p`fYd(q&_lE&+9QvF$j7r8!H_5;jQv_6+e8w4UiBYc2 zy*B~nv>~Q{!4Kv|l}`RbM%pb=wGPPsVd06>#PrpFks0<{nyqx^lnIt9n*Kh`TPa(v`w%4d8oBwIREEto*BjGL%vh#_zMc z@|Rh@#+EPd54lpAq-tGNF8b=EH>E|%56`JwWS`^DVbKHq`76a8AQf8wa@^}8sVQTgFZ1+;JZYf0^hw8=^_W2@u}8t17m2^v(~^CEmq7*ft7KSn z=56=qPVX2TTh+H#0 z06$5@o`rYy#xZ^EkGE?p>hksrkr&@iu+WRpQSfZlNoibPSNn<`uux)bPbUx6w>dVu z;zZV(h9A^?DWFSY_+h{uY4Gjp9p?Jii|^OnG@iIO+Ws=32ZUdIey!u%d-*=iQyo0z zC|&k5mc_l+NbhPG3ezDo1}E4>bOYVr0FKP9#~=-jKhiFj;F#iFex2wZp$Cn;r!7KC zL+ek9rt=sfBo>R14?p%=S5U6(JDCSl-t;q2>_S59Fe?PTz_iU8ZH;~Y@ z33)m_1lVdl7`H+I3qt6UQ@RhGCr#S%Hst?90Np*rS%h_eGK457YrTY?MITXFSm@!L ze*REau+I&)_a$fRrutsb8X>dr>$#Ne%-^a!nghVm#-TWB=9FmLwMU?I@Z5AAgSjWT zV1Epe)JgK}ReW$RNA%!f1oZk$Xos9*{Sfd96^VGz64_>`J%hfGQ2XMO)9YeDue?8L zYY!M{mc=#?s#N$!+2SVC?AC2B8vY*asY(OP7AKh3`DR#5=#kHcI!W2&H0NffO-Yuj zJE;}+77eTJkkar{o@kl=4kMJmv3*6@W^Jv#c%>WL>1+Q-J))w*L)=0bvw6jJgvoK0 zzskWxHAvNe1;2e-C68VRQ;^bbl(XKC&8K^}Fn2||=K!^k_pRRV;waV;R-NQ zDLHj#Q?P#=s-;vp+DQICObcc({!H@lARWw)#2A8WemWq$pHD?BfGf#|j7_PRnPM7w zvHvFqn=kFuXI{p!r%v=upZ?j~ ziIkoY6tGi+wKN`vG({;s@?Yp5%8M?*QA_$mtPbNfqE)QPgGRn~KU zMPw7R)1DS=WW7k7@IE(OPB$?kSjCFT5%U}%a+4vUE2r=)=v2P6CW|j-E9?yYr0Zh* zKB6Ek%)jOjkgU*(QDKwD%aHmIRp0f`Y#$L=20&Tz+>`xp-m-*zD%a{RYIHNW$lOC( zK~CzGDwe2QLewySooo1bcCrlUR&2c)Tf7+0|7k|@-BTjqZy5$H_EZugX|pAR5GT6y z2;CXy{>iRlC2ShHf-XtLM80U~wqZg_?K!{cb?!n!MqN+NNk&&KF?Gj9AxS%nsb^?H z6;Txv^Pf6Qrp@3q`p}Y(@8L@bCwvx@h{6_-GuyLZ?D5zbzT0!ZR-&Hazl?0bA0ch!g`WtqjoKe5b%Vt zM`s*;`Z&c*@yB<|7#y@HiO%SY94(>smp1zgTFtf-X1YSMS zA|&8zviBGd_=8J3hxSgBi{Diz8N;|8RYz8!w}?DaM;ui#NhblLvsh{k{j^zDc(E z;9?B>ON_o*k$6$7m+>-a(l^AHvz_KCc2h_C1T2C1+`{lCCpcwu3d9KL*&z2zK6NaC zyL$2!k|x=qwx!))vsq`n$9g=I z%CGg-?e}k8b`T0z^nvus$wAbcmN5Cn#{i60`>>~%1XslUK+~|uDeAt+PEh=&SmWY@ zgSw4C@~~=6%7;4%O&f z4z2^_w2y+H@kmSMbAF4Xln`y-7%LH-n;HBdO{(4v z9TfE0*oGmxseCEj#d}_&_z&yAfS}-oC=Kn>9{H+n?9UY%FXza}?GOl9h!-nB3uOvB z;C5<5<@72awQXMkW0?vAK|{B7T~dp+AW=dd{!>kr6C!(mhX+{8`D1VrKe7d@()4u& zoni%Yvlh@%LKC#<%!Ohw{?Ek}|B#%B=CQ0y5cS1e@*aTZxi$#xevNMm&Hw7#8{;M2 z;Iuk+(>>}%PyIw@V=R(ScfsQp3A+H+H)MB=Z#)j-Nkn-Wb0V8%yhAr%tn z#HP}&`w?x1tQ4C~y$V}d$3GR>NII1)oRkvAJ7GjR>BJh%e-H>Hd5#esCl}w%Fdg7% zOCh)IHVvpktFr~L?PnYZy0qjzXr}2yM^aUtiPdY`qwLDXi^jB74Hj;X58DP0`zcIq zO|1I^!Bkj5=Ar5$G1-@eaHctje4ojAGNYCn{hJ(qg#hlW)n-wOK((qxRPcMhUb zVb-R3vo!PlNqLK*9bw_l?rWjg#xk-v9RxwV_4lZvk^tGB?g0Eie{axUj{na$WookA zz>6`*)5ZzLAEok-@M#rKmZ!=o6H3(!MmdzSwCF~2{GO{;l)ARW%~6y-s9)Xnn4UG* zKO%&Yi&e1Pcn2|didiyEA0KpN(1WhUR}lw z=G1!a@9K1V`O-};u@w<=EV)Vd=5GDJDcg92`O?n{t4^<1G*i@7wM4IdjEQHb`K@&7 zFiXfIk>{L=EfJ@0UErrwAs_oXOyTvCzn^$RU9t6C>E8J-c%wdva~Za- z+)jmwfta~A@H#|`*+L-JRz2>@%v7uusRh~MR4q!ZmtEpm97<`FCN_Lpq8Y7T-CsH4 zWAK`snsQ4AEzu4AnYtt@M;m}UAAyu(2GcP=FoAru-#!Allx;6FMXuzshW@kzHs*N_ zl2!+3P};KII$#lnz!FW08*eD5g@Xr|_w)yC!+$FVgI`a3Gc!1X4*@1i* zy239N$#sTHw*}ndG5sb00v6;@{%w3 z3YM;=S=kh_eh@WWDl>e!U}H&%1L>8Yqy(%1J#q7Be-tBA{cOzjaXqFm zjetg?(_XBVbA?dvNjIWzs-R;8A1Dx>giQB)>IFa1qJ~f%?ghO)l`6PhN!g6V#m--? zz=O%0zbfAnEua#q6;rbppw?{)AQs9Uh98upWe%Wjn>96?%*raYLTTZya>aF3VPu}% z14j6-=K7K<%xM^Wh8gT%^N)pNDhM~%?+!P)@iVCwZ-8-PkreS^tHGG(CeM_{AWgTlP5=3mY1b;r26h_#1nYjX#iQZRGmP#q`M zjlWtwp1&K#mxj9*)&X zJ=CE1Jo5BT1%__kO3Tz|z+h7!Xozb`1>uqu2a5}7fKzHb`FoW!(AvH6t#^mS>iS0l9@6X$Y2y!^NXAEiMm1M3L zH9tpgu__K>|G+kcIpJ{N_T7Z|x%g%-zF!~`cTk5Ht3l#pM>KFh5AAX?yB?$_anzr@Xx3;md$;*;By`6;J zR<+7MRCN36xu_tBJB^48V+~HTeJg8O=%l#p#FC|+LDI6pBip`yFI-@ zQbIlIPRm`L%jy$<}UAnjQ*mva@JfFSmEDWEyv~O4r*u#jg6neSJaU<-$%bTjW0YIW?y7zNf{68y89D zM{SLn^ND#$q3)p${O4*Pc^m@7?_|EbI2F!EkL;zDaBn zm+B}+3EOg~N6CG$KwG@6wu<^k4{k1l2pe4GGG#k9nKPXpQ{$?dh1apl8OQl*{{Q$Jb5uirfyTh$B2>UV8+Rd_+EJfsY z+7}~IvhXCLBArtDlwmk%Oz6==<92{s?3X_dTc;U!0l-}c4QFSi$1YA(W}_MN0tL73 zXJ<{7X4Og|*E8^4!N*j=$1=gkeF`1kn1wRP8ezy?Qq0r0JcLEwHPFsKV30M$0*=6c zbiLc)RREk@67nJ(2Nz}`9lR5FUj>7NRO%8nn%I2ic27o3KhEwweH!wJW&~ZE{@87i z4}4QO`Zy&GuiyxV3~_+AYjnB}9hHNuwdNeiD!C=lF4I%XpjzQmx`x&#Ro*VkR5q$= zI&*s+hdQkO0$OFz?f46wW|B*%S&1r{-=91hrgIdrfB(*}-4F0u`>WGjqJMZVzRW5O@diP-k7$&g z0Q|XY{ByshFFl`lPda-lwy{+%c3tR%%aY|UI+{{J*R1Ggk#OK1nhM~2C?-1iP3Z6Q=J@LF+MaL}kVUn$`a}K9g8?%wSWomzUL=(Iz?|iW$9un}qM_SmZKNu4A zdOSr_goGg?=jru}e~{jllk-Jbtr4I3gY)2=__jxMU!FIId)a6#g%{RNC&2{)A?x-X zh_T^&nN&bteuir4MI`C`OsYBuk0Yp;f9;KId#YEkBM3~#vV2#ifgTL9@pj=MIQR5N zm<^84%0`^btMoXySs6UN5`ed;z-a8PwNqBu~%TTu@o)Egw%zsy<|LR4SnnUiiKe1E$FIx zWvOKn0b;o-#RsHi|1teBdn;hK?s3nEI1-ul@lzvy5_f@>Rz9)X{RwfcG8M>w3qF?W}j`rH2A5EuXLl$}6|2DpdqD zO4Q29iz`}`aVr;ACXQNu?F&N_^cO`$F!}g5gurt~wH`-B%JGKLS2Cy77h7=)?__P8ZpS~n%9&cLo?DO|K1%1YL;+drZ zC(2Lv@`C~pFi3Ehkeikb`y_rB{`T(7ebY=Ik08*Fgh!A2Z3-tzVBtk0Z|jA-Riwh$ zxu2D(QLqv~dv8}H&_WRAk~a0rW&#`|v9FsHEEVsryY6wEmDw|}<}~himK0-bGjnZ7 zLF(8hknPJNR40KiG``LF@Nvfi+pEhS6ssU@AVL>V0D1DXZ>d`q(YxvHFt}0lhe4$c zfzF^}gDy+wH(r}pmCN15SsP@RpqHkVla5OoxX}TXwO87H5J>|ju4LfFjXq_Bj+v%3l{1m9!-idH{JI43WDO9v{I9nUQN(KlLfIx3@-+{0SARjyeb3Vg|K-jH?Ga4&A!!>ZG6 zw(8qPFAc+6@MtVwsnW=EpQcC0YiSPQG>QWZYKb_yayjR{L2IS>mzD`_&T=RV2l};d zm;O_^tnkz){akHZJyQ?EXUXPq`i>sqq2fhhVPcmN)0zc6V!k#1SSwG<3AwPg57r0Qtr# zS`~#(?)iqrJca;EZGH1nr=WH(r!gDI2|N?;>`S`__OBX8SD(Ro&zSsdDlg*ojOD(C znTjiy;9v?HDL!$5jrFRn_zG^o*X+Z4DV-_U?TL98Jm#b>QcmjF<9etX@r?~!e{)&81}%fgZCSygA%ket>;R^ml>MaP2ng-vvK zNl7_*hQ&V3HI4|5b87ubTK}YA)T2n z5zKa+uloZgMRVq8VD0AY$ufWJXVA*?r)LvcrWcYd_mK5l}_aREQ&mT8ZTo1j2z(z=?)U*xb3yDLlbPq9;0M z@Rm3JhDhlMP2>d}milo-sZD8zXVEmc@@LQH*g&Jpin6l1ee5bKw->>6x;ELk4eDwm zg(bj&TN`Qec$dIkSLf>%+e4Fe1k1TCMGsYRAtN5*-C$2Z0CC8+M9yv{iK(B{V5431 z4nhC;EcDUNhUT}#u~)GoS^g5RmS`&RYN7?cn?7UrQ*`4KSBwTR4=HX3!^_%c=Yj&1 z#t!fo=g6=`UGo64?UFBH!8ROqF5cU6Y)7_qWxSTgWm~IQA8xO26V+c}*4}KP{e!r@ zJ3C2{n(}@|poNZ=291+afT@ew$!Xi-G~O)&Mx>tUu)kKGtyfX1PMZw0S#oc6fwWQa zat1T+4wYH#ZcHsKpDi5c=15FEJUn(3Mrvk(m;LxvTvTUW${>?QSDP8nK%sT$;Oyk* zCg-e4ur&z|+eM{rnTw|x2~S%U*8(QNDpm0dl@r?#c%pavs%~(AJ_7WI0pZ`8wjp6Z zgT5?K61chg$1@_i`e<(Un2=|!WT1t4=X$4~O2-}<+fFhb61_4LHutw&|96>|4a3CB z5>>bPv-tPN+bM2@xCq^?_U(bsC&jXbZ;b8MZDtz+4#IUFh9w8~f^ufB7E}<~1Ju2~ zhIMrJ`BnJTL2S_!@-}b6b+s0*Pp*E!NliO-(;IYW$K5QIE>2rZ z@Vn0R>yN}eS+pTS@)A!pA$f8b#|u3I-F4jkKdEiT7{VhxcqP16Rxdz$c&{jUz#Lf+ zqk#Z7XNAk*2B=?qt&x4^q2k1INlR4StEG9`{PTNXOQUrHtljJ23X8wEIJ7?gu2n>` z{~!}mo4?}HuG{VUvF3B$AhG+%=n?9?7pRctr;(Ex=&`!-_=3L$KEc297V@Qg(RXRv zYHR_jx((5T1a{?KdRFl!gw#2w6nav4Uk~h}zsj3TdkQEv>#Ys4tLvgT(@y9|ZlCHz z!Eo0z8~CG|SBw@K?(a5A=B!3LxZszJahQz#6m$^>g7~le+UCnOP{ek(XnC zM#JLDHfr*MD>MEx58Dzm7jXT70Z8?S@hP?Bj@#_3n7vPV0RXh)tkh)Jkc$HnQK0wQ z)fK^Wb>A?fUFQtX%G@M$A6Js(E9lOBH*&Xw+^;h)(YJ6+har1ceJJFiIVKfmZxOII z*g0DkOn-cBs&+BUGV~O_#;F-XgFX7K4HLS*B87(ogN?ggLgj1!0rXZ=Qj))#PoVo=6F1p25ug@ z_U-(Q$9$`Qq&EJVq*-sDN2#$bten(;w4x{xP-Pa)AVB^HJ$jpcN`ZNFIr$PG_l(05 zoa2-SL>(P`N?`RK5 zTf%k7o1n->2qb!@ixK}_;9Yz%9ZeiKK=l*$_?mY+GwzAvhH%ZHy1^qHZmR}&p$qzq zN1!~BkS;NcNr2OucxA9&Y0OuNlw!F2tkC5Lcmc-A);ReeVL@hs=d!e8=3;ZFiGRiE z;zm&^rLNC48jtA|14?lja-DjdPBZ~?QanvIAJ&#Gd|*?SQe{mCt1P%KN*~~?p*wG&V3dUcPy$wR)Vmr-IDCxsYh3X@#ynO z<+>hj$zUdxjf}lr0q7`xV)}iB;3Lo*_5>58S)Lt)o9)K9oHWf_$)eG$dB6hqm9D$G zrn^p3co)J?#0-(&ubuX4u5e_x`5}K!btEtOsV+6SvI4cnq&12?V<1UtPFC`ks8*R1 zOzi##36QGeN3QR$N4&J7yhmwxmtiu2Ti$7ze?!m#6PTNLKc$ao9A#HZf@-diW8!pYQ56FjQ??} z+TI@xECXh|?Yjw_bO{`dSkl9!RW=28jJwJ++o}hTypjcT=#Ot;thQTPIiCN}#YP-T487h5VJ3nnlL8c_bO>NgATv zPqcexn%+$dwZ~3WYs)53MXPWX-8RJS)dOH&N_qv6H#UWvj|5fzH}K!nit_P214#3J zi2&^46|91_qWlkxKd{xLkHBPxiI!^QV|@7dW7}Lg|o-Lj}nMyZ5|Qa z&doxcJ2rCndEaOJOz)NTLFNF`Li)c&sAgX;wXc0@>ESqI7N*%}^m4+#6p`9rx^-QIaD0L}wCU|X4V`rL{E4b; zTQYX^O^DYi72T+(+5Up0gI4RZBC&97axY;n{;86}G4FIg5kKn<9cN!SbM(tvQ(26E za*^k55)eC*#GN$4-N)g?a}20MfsrVFN0 zXb!kS)z@bScp7dDjnFO{Dpps}<$~M&or6(`&%q04?%g}XT8NcxM71{WV$1Nwv8LyM z_2X5CNi76BS<(pTWxF}d9P!W9drL%DRG&M~O`1nILyk3#8KTME59obXYnwep>$bym zNIxxs0$II8n$%@+tVCll3L~l3wkR`l4EK+xliJq5L#D_zh#Oy1ACJeyn_qyU)h05>~2`dB?p38nJ&a=MP?LVaLORo%>0 znmuj1g$(9rp#6T@A?ljLib%7JKxl?K=08o^P^kXN4F~YE!hC%o$gu4@J#3xZNHHY? zuXh}m`>!Re`|stBnd~cQmtl~34H#|n(7-{jK%4o%B)T@zy_-v>TxMTfOBQtvO3*u4 z@o&PY*x~As{p4qQcLLy|Nqn|tU}lrR*!FF=d$056u~WkLuPnQ(68cO$2k?d}yrou^ z&;7c*98E;1Q+5kYTN1cE3(HWat!4WNJa-1 zIR7L~Yo$QLjGvD$?zqp0<qmI^sik< z5=lBaB^>&SSuGMx){-zW4vun0jiL^wpu@7TPV?J_meaHtn$*&}KD<8mH&6462QG*) zxmq}8QM2g%40ut?`){FsdooHxXRx&>9%Aid8#m_}{evPS8fVcLGryUA-e)vCQTG%9 z_&(r|N9yJ+yE0?+Lxmy?Dbz}5R`BYd>iwB4u0!A7{_io1?7vRX=jlw0e;LWoL$lZa z7A+*C&{%?hDS+qc|NrOzzZ?J0c_ZeDw-*UxjvWip#>oH7WJn+1tG=t1H4FP+7K>Gt literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/images/instance-wizard-parts.png b/cosmic-client/cosmic-ui/images/instance-wizard-parts.png new file mode 100644 index 0000000000000000000000000000000000000000..70c10985b3d420e9ab68473950b4fa9fa7b7d8c7 GIT binary patch literal 78547 zcmb@t1yG#Jw=X(_1_%-~IDz2qFt`PRy9EtQaCdhJ1W1AgmjJ=t-7UBb1lPgc^-i+) z{@+u(-ns9dS8uAI=bLKzeXCcmUiwj4Q5qeU2o(STpud%oR0RNl`>?;UXGpMLOup*W z!Wu$nDQ#ypyARH8#*Scsn5o@+FvVLNV>7TS*x1y=VE`-$0KmVtP}g?WR*(mo*x9fc z|L()$ZetG{4FCv=xZ4|>Sb?1>-h<67Y=tO~n%XESEKG$cwYU}76znCy<`y!Zj$k!U zMRgNTD-(WGN)cfSL3a>r0voWiF@?L0wXGA#U5N6}ydc>3-#@cbQvB)SY$Zhb7btB7 zWeN#9M=%993mdZu8xK1L4;KqN7aKnpKNAH98#@~-I|nNpFEcwEh?^J0#!m6~hZ44$ zqv;2bs^pu$*MdC>QJOnD+k;qH-Q3(*+&Ee69L-qS`T6--**I7^IGABQn4LUqosHd@ zZJnt8nL!flWa4OH?`&abOYwU~UMV4|4>oc+|Jp~$=uGKLPCw3g`Gmd*u=v2_b-1&C@6s5+B!KK+nRviN(xcJ=3ud~ zFa_~)@Ni2=af?gv@^P`VOR@32;pX9I=ip-F5#!?!W0U%4tfZZZiw)S;`Jb_-|9h;& ze>?U!GuYU}R+a=iTDXEu-#FUYQ2cpmki~zGh5x^u@9(ju|2-D5|8^`Z3=He~H ztkP){JGh6hvm`*^ul)l^pDNIkpa8{~D-wXn{L4@5MkXfDUp{{c?~5eKCKfC)5sRUm zpa{|*TeMM(HS_$?c=yn@ySunFH|NgjW!&~uFjd7V#5rxetF^3U=(EY8pjNKUgdf=h zs@vRBYkyw>dw@^r`L8V!PvoEHf3?r8VWVLE{?)#m{XN_JioaX-a-BcV|89+{{;c}` zUv2w`-vA;5a_?Q`k$@;zXSR|+j3}IGr?}aLm$MiT`SS0lrH?MD4{s}jdOYO zjs~l6J8eLXAdC=c;1)zM+8{(3m5x4cvGde&S|x&xKkoTp{cVcz$R8HgOu>`Bk&aWN z^NToDz$K56Zc#KU%{He4MUYb$FVzuIXBsJ%#p7)V-u_z>c|CRtEU%@d1q2FNeCzf_VN+Hs5n!g$;t+euldH+zI~=!z6DT;&S;B zY(^ITly(8LLzA1|h(>2gs!X763nLyD!g;84G?X%(WLExc*|n3HE?SvYGD)iXy$eB>!fO2Op8a%*sx9OOdj`)&hWm+A^3R zl>g5`8?kE9(gvCwNq9Wl)C%7G8clCCqb}tZ=b5rAu1uyGQjM`$hMT$^ z?wiMNnNZb?2Qsk66Z_zDQ6(0h2LJSN^8vjKLz10VAGm35=>4v9)u+lC^gais4fo%2 z@9uu=>gYx|BiKojciH(2T29!(oyER0`h#QsktGb#hK>#g;f{!D*uRUCHqk{JxP%q^ zVv*-(y_lzgf^37%z2$nB{;gywbvgI6 zhaqLv*Hw#yx-J3{HWow}kyB)>iYs+*0*ruNY=X&oFR+d{;{-L5uHF7N-YLgs*9F2$zG<;uiYE3svCUvE(PEM+H0nDFDhUk3ICeDqpLn|VZ-jD zvpYe97(llbSzSILJZANTPnZh6-|VR6Xz3Q4tk1)*%`uM?CU9Lkmb)AbW0km8mxuzBU_Lz8jZcZ?|s{kUjRl3j*-P@kpPVV z3UjzXaM@@I^6NfeFS`VE=P=gUP_uI^+EGIlQOxvdY_pBza1T&=yC zu4dO`2%lTSL|VXby8+qf1kHooM7>5b3UAiT>;3ljb_nGcnP>Mu0$G!TGazy-7LMyP z^2VB7S@t#Gqy({JAAs%G7O_O_MdKqK-E%3I5@?c4~R zX1D@w!*v*qNsb2v7~#!Rrn9d2NTmie&u@;9HeY4dhTqPS0l)nOj~)t@iPNrz7HYO! z7362!V(ju;bvF%dV*iNb#YIKzA57?j$C;Cca))9pb6OQd4jH*W1MR80^7rmG_ca{;pN0FWv=Fzl#+D+J6C%VT36-D}GXDQ}4U8 zL=X|J3O31ca1At?b6`?r{MBX!Ese~s*iLb1Eu_k!G8A^bpB+fJP!}ZA;=<02M%uUb z^KV9WM&CFQHR20Mjm}qupxo8lKQ3DOVTvk3yCe%%TBNMe20^SV?v^k?2cHr!4B~xd zV|Ie7=XT)oq+K+9OD=f6BAtBj+}sPm&T)M|5sH|H_@X23@Ld`z@N+t)|JW>}7)d!N z00pjL%dyGQ-4Jh{L5wtm5=uR4^abbM+X_;waDIM%kHU06Ry8Vo7h=tlVl{{2zX256 zw!T*3B~L#c6O+l&qm`&NkP?x~F=9)FXC{kG;lpI<6kgKIe{gI_C5!Ai?DS#{y6_&Q ziV;3-sd5k-V34Mz3Risd3@Q-5ev%XWinB56Oj+8jC}MqFSqXi{y`ibd3;i+&P|t2G zRW#o)QxK%_LXESX5PF9`+WrA{kw9YwQ74v)F7;e+i1r}(s3CSjx7Rty1a|>E{qc+m zOxioqJn{X%Vlj<|8ue{sd%vJe&F;?505C~2>7_frShjwK#E=SXEtVU1*<7a*Y@OX? zh`3xA1j#+-g#1tn^h8t{1q7tFhvhGt#;LVyxtX~&=HvwZ9634pc@639?L{*G^i6Pz zPnUItuAfvafdxc7?buNKh4G%L8aedTb{nGSG7R)j9cUb)_KG&B}I2tI?9h=>8+8CT;#+((}QFjO9zottngO)Wr>xZk)D z!e;2!0ca!cJI9IGz@Ne8^w9fJrfAO+FRPoSJq9!nSaLqtmk$A;X*#7s%f)D8I{71<|54tq5Cvcvvstp@u8dky) zNMYbT4YiyNcjjHY7YkfdhK}-$8u4YeiMCO zru4R;zU~izW?d#cZtZy&!MxkAyyC7uj+5X9gKa}X_oj7gS^=MxQtNym`%TO*pD%sH z=%l*dk6Gi@HP}aKXAi;cFX$#^t1EHz!e~C=DGM)Ung2z)I!Q!Sz;{8ToeG4ks;ZDZXy3PZw5k;E*j3XHM)HTW>HYXin&Ns^Qn7Pkcl@2MM#qJ`5W ztwG%^qnXW~bde7>&w$CH93-@@qtmRxWteSm$HnsL6 zdfwXw)?8J^<|{rPRIK;mbEeU)06b<<9H*v3M#2F5k_GD7CCGhJGxeAk&fhS|8v|?6 zZMh3=HS6r=TU)56PvF`Q3%p-FeUm2lsy1ZO=fIY2u{5*Kv3coos*s$%f)}GG$#bpb$f|l$~b@W#{DB_?{I9BHV2F|PZQqkqjes%F% zm*MT!`NHTg_-g!2#V1b0TbEz+#N|gTN+a<%KmGmoz#RgkGtW3B9^yp3cBs55YEUjg zE?i`?sN#T(o`1+&e11`bI;lflxEfn+`NV|!n%n~M+ohtb$Iwye{PXRnY1mDR<6pfL zy6#4AFP)0d?;@&fIOs@IUHAcR%hrFPq}V}3%3mr+rNa0jl%QixDJMF^=XfmWYVB+v zG)QhG;9u9i9=qoj6BSiL7cckGh^?LgdWlLR!JaWH^2v%bT2AE+{hIa}-^^FJ`dyZ~ zB9^Lb4?!;Sw5@n60z+qq8ayn+?&?y;kQ(bff)!!RSAVJp1i<1k31tyGh0WLXxz_5C4uWNH=B@1;L5vgKF97g zqpoO77MwSla@<$~Ha|WFJjQ(b6diZDzr?vZqbGY!Zzs`j92KUlg?iiiV))@A&zP*& z!3VjVEbnHu6ZNwri8wGN|Qj7VN_olU&fQq?s);#Ii*5g4*k1F z|BNk&3LJ5A5OWTsLIc5~AmW3?AaL3Y9^TRLkXgjiY zgO8_oboztqXNw+WRnjiaUkhhD?0{ewNF1ImYtq*jlLUU>-qQS~J+LBr*BYOh(UoVl zJq=LbDnhKU9BERr8U`zr=_Fq)&I7}q0yW&=M!$f~q><)3q&3+2g)PE)8Z3$T6qW

l57@TqT4#O(}qsT}2dEThRtBF;LUz+VPXwMTF|SUGF)Q&9`A1ha`-n0;1^}_PleQ ze`NFl5{(XBlgbifaV|lVIu*FAN>4TFtDYGlw5<`rB|FrGV6Z7kYOX8LQg~WQy(2NO4fH7 zF_5cm6Di0*Lp582RFvw=>H&BM)eMDcfz0?EQ|;bFn<%M6PdRX13+OXpr0(lDv3pGO z388Y_g&xTLktO~VyQlNBdO@dkbRt&a+pav{4@2Kf*G7Az2D(1pz*5zT%ytpe&6fdxEM%P2^NvtYoVk?Cc+|r3ee0r2)^8=Nwpj&M1DId%nSgfbK0^V(imozU-KOr~`T>?tu}A5q=k9vil8wO=y2$J@s0>*94Q+S~Al0AJqwdpXKh54r7y@w%|sK_DQ^o~zdMquS%Pp;W_{V)Lm3<&@~fhVtz^p%J=^onGR6XsFBP+Vf&jqD2V-jA zn+VHr35@Xh^`R)+9eOd_gVMIHSka3oyf|pq*m>h9z56^xU5UMyfP=ny>z(_Dg~U;) zP>@JA}np)Zh|RN-vm3=Xp4%2*Et z!S!0om1|ZjE8FJ_Oc-b@Bg7G-hIG0ig&~ux4JgxTe2CgQctfS4#e{#Bg#ZT=+Jbpg zV-v|^<|}8r+U9x-=93;xB*dqJsjAS@N=dY8hoTW6|*%+ zaQyXp(DgzVQojcgh%#ykp)BW7Seka|=cW^aJ$Y$-z8=e+IZH#ZyMBH*HxA0qmdfzQ z{(Ez-Lc_|vuZhTT0SDW~DyXxpIU^TRBy|sCi^_IQIBSmD8?MiTmVsxPNuLf&`F)8) z2$>wqPD`7gZ3e@g$C^1Fze7Y4G0C;cWd-btsL0&eFw-iP)wEU`Oh9zKpjR`I=RrPLqqG|&(wkN}4t z`__S|3fXu+`iZ3J;nxrmPwwCEmh^584AQc8=bsRYq(Z$?XVUY>J=I6=NP9xT>)E)M zt_CbF-fpx!M_377kA^4dN35qwEzu)VRQ)0A7bq`Pmz+ffVO|@bT-A@o_JeY^s>zzN zR)t?@`sJYhV2e>augl)-3IX7DUr4;3*$>34JG~}%lFzoyiX+P*XCQvHu^El9s}C3R z*9?O(?`~4Szxj9wZKAAeD};``y+UN_y9x#uAg7|@RDj~p%D=wlku83g1ixfx{MhTt zpyevwf5)c$XY0c(PRxU8))fhntJ`amlP=U)D}9`9dWyGV&3VM0{4bvoeUa(Li|GM7 zvzN4nyr&CSgHO@ID&yw2yDRr7s{H&&c=xGhMYo~_airb+mnw3f82bE;CLvWYtelKBRf0PJ-#vH6d7ooqfs98I%UAs2O%i~so``ndn+^E?Uim+AQ>!VQYP>7s+`Q5(*y))U( z7j`_Fg0{~Y_@GgHk;Ph!7%``)V_3tRg(FYV9F^)qehWs zwVv5?=-G7pa&EaQ=9;%JrV9Zd3j<5r=+ViIPaufgwr-wqWs7M7y+sn}sqS}rxDaqk z_i!I4-E~mjn-*Z7juhGA&|(SIBshCE_3m}~&aK+iJ#ZQ&TjnXpY!7+lw~aZvZe{Y0 z9is2G_!PQX@9J!Q9t%*!%gaW2snMR}O@}*}IX+UyS(GH3G{IK~F?reB^z2s`Ro9cG z|C`0YayWXw>guTh6Xu|HixTiXymvKqpt}!D*<3~6<83B{(nWFJShPopV!LVAg~Cs` zmTQa3lTNxJkq>ak&m)K~*Lan+029uMcP~7TI7fxgc1})CbSbcov#({y29I_eo2JuL z`qmlJXW$?G-yZi0iLF1y*>``>@<(EvL2+n7M6(%dc{@Ia`$@XPChp~yOzguxJ$QtI zEnm9B8qQJR^JwkN9y*qtWfUu5wvJwA^4>4`)k%B9Y0w|r14{lKe!kAeaD001B5~UC z;v}ap`cX@ejFx~3@np)F*|J)c{ed(}Qix92@uG8=W2`=L+z1-yqGs*V%}dk+H96a( zTNb~mgtvcm%q0QD3RTB_*M*ymp-EgJRTeX@1_Yy@5RoFQkUUN6BAo1MXc=7GE3=(b zruL9;p56HQY_En@>-d|L*&QHM@Ay}jmWH)tEUHw5!S6p?>k(b3f<_1ll_xKUo>fpL z4*Bei_mTUE|1@dk1eqJO&js>%QgMui$BW2sEh3$}G@Al8ShRO&4p9QUy1LJzG_9$+ zqxegOqShxst3@ipkq>Qdp`@Zvwoh8}N=7CzF#`G1b2IKO9jIlI1s>CBkacWhg`OM= z`BRxiX5I2ozTb>*klvn2C1?eJ>-YSo9`nF581tPIk~^pghk) z!HT?nzt@ByQ=Sxqa2y?NjJ7YeL?cxm51{yfRoPyEHFm*&sNl378ugL82+|xgw~;)SOp`N^+XSiXvD-Nok}PUYDq4#dmqoX`-HZ0Mhsx4yn;KqR3;|F z*q4-fEVZEdgr6W6$wJ=7=`^4<*H1A{s(z`b^71W-3=sMJ?^tMnPjsr8r86Mq!ME*6 zAw+ytgxur&3$pH_yBZZ~OivKNDLjvdzxS&J>Dqh!vP5I>G|-+`jASJ?G^t!K)Jl0N z?9^ERKED4e+>fsEObcc!E7Cp7=@{q&peph-PdT?AF8?9+WW#dm0z;P!$LNM5cT$oT z{N6Y+HzL{=?!+Fx8$FO*^nqc@$*Mr_R?N@u>6I?=ue}qb{pe>Nl9{rEgPTrxgG9Ba zr6ddF9Y&u)Z&0RaBhF?Qza1jr>1T;o*!$6>c*sOWLLAVh#|Da47a+zT=bZ*@5*sH= zgv&C^@iY8()yD?Ss_l^w4TXPY(>eTp>z332pOs#Ca#8p+-@^^3>O%Fn8TdR6L@)-_ zGmWy~Ou2$oEklND>tkr5CTDhl5=yi>M-7N5vK4`XKe$b#1cZyrZpG?qTqeQPXE9I8 z?lYZU)E^iDH}fKuk$m^gu^r62d#SHIvnvvz$rN-sj>OHzNOrAOv)bO5Ipcb zeX^gm7fyMug$_$e;k5I4g6Yw-4<(~}XGPQb*5O(f@@;wyi4Nq|@N2bbBK5z6Gxn_e zbO{ld8SaPOO7tv&O!moyVU)}c$z)+q&j-1X53Jd&~ zd39`F^BTJ(lMZ8@49hl^n19>$^$uALEq+wROfnfAHwR+tq})0U zbVb?1L)m19kSDw}y7ZawBRsSPa`AZOlNrLz@>@a4sY7VVeHI$G)}EKCO$Zi>t-h z0n<0#B=z^MKpA`-i>h^OB3frlKG3wRQHB^)p$2{hL(K_ zsIQuXcsOi{qS#}>Fpcps@VEbmvm81f-lm|9#&pl>4Lu>ed{y7=(7JB!Z8Cd1=H3+9 zla0YQ8AlsK!2G)SC7inqV*HnQxEi*`2);FJPT9Jo= zFcS2Fx$m~Z;D9;JT6AdvsEdM-8M5ik^q%WlT&Fnv*W)xU>TOG7M9v2zvb!ddWji;n z)FFeJo^!#85lEV*yUQz+itEF=XN=do@7$N$m@n&Uv%NyhdLr<+E|@jTrg8ioaUr~> z$V)2w0mei$C0GidgYOx50xBDa5gkAx>+9>wM|*n$G<|NTL>Kc%{^+-Hy zw7muEEln)%+RblQ)!6vD^ifp{gWQQg)vHp~4&BL~5^yp4lxW{F-a?n6TytS*&sj*# z8!l>w8^MtiPBV8_HkikJAB6M0;{dbddNYck^5%4}~r zx&}i$pxBDnNR?KOL^&UUFs_$T)+Dq11IXe9qBcm{*u&@iN$C11bbea9=(+i&ZdWay zZNythRT{CtCokENccDGkL~%9S-rjfpRno0W4w~;d3Y$n>oE3*FwT2u&Smq$6@1$RS zsQmV_QP5(FUeRLQbLMit>jU-W4K_$>Ie&$5TwLo`@QAx1x4lD~DFM(Oi%XJMY%Qyv zlQ>AtYS4xad6b=UqDUDQ7EQw-G}_MAQ2j2@LK8bZcF$*C;ODg=RM*!tfSo^w3Oj}+ z3e;-6t0a>QR@IacrFY&3gue|+4)|Jh^U8%aFV`avOhiml5#Rge{)Gz}&dxol95s}% zE`Dw4oG4Gk&`5GmK>%rrN?||G#$x9NE4yMi$ zJLnlIxzvSa#k2=HVQZ0BM!>|2GQG$1uug;K|kdWU7l~d}_ zSrFRWkZ;NrDxbNQ;9$dbx?jhcv$LU)53^u4e*n9U>*^{-9Jj$Ri%Q=LQrg0e`#jen zaw)3O_h6+3dH!3}AK$a#N_)EY<(J}Kn2x?LH9M$N77a$ohTIcorqd^?|p8s zfzH?deN!fusVEU~g75XJXIYU$XHh*fN_Zg5g2Tf@(#5pJIK*Kg zLBab2YZifQxR#CVR|o=nnhj*%WkHlB5>sk`HC$?i(nU%PRyiw&oDblshF~vAs~Wu0 zP^zVmj$g`qosQB6_QoNQM3~#xLNDmGC`nh#4t%D-NVOtoM@lXB8}pUT`)TuP+$c#j^Nq;aDga}U zUdQwe&wexot?ZA}cKIVGQv#~JUk#r_gc7V{2;Sgr1ik0b{sd4&xAZ0Z(u6YvK5Y81 zY;(QG9pb(E<+rz+UX(qj=8Z$ohHWkIP(3{Gibx{F~1_j2qD;z)qz8Gm!RAzfue{57%5~KN>2>M+fhZ}HWTbni$nZW(|Y8mTwFg^X4 zJ563Q)~9V#-F9|71j};1U4T0^c63}toApWsE6}`?L&AhA{~3yv?&K3F)|7$)i;;n* zFIgYe*|j`LsY9rswiL-frqFZ1s3g+WVCq{qt#4Ry-O|!h(;f0{*vLjB7VgJYMK)P~ z_|@v%GZ~fs_Iyq+VyRaI<)h{edStydLmvmelXf1)0mdL*aK5iDi9x<=*N}>%Y-r46 zNKL453rAF;GwZtiw}i-^&uWDh!3fi^^aN_+jz^-}9#OW>U3-Z8?=nrfr!8yl&pDMK zM1U5w&8s*_&dwTP$yi=giXcoQt20HG4GbjiFe4wK7fW5(Y=oM_e318uuK&cR%!{grAF4O2Im$&H38w$lV$mr!IDfO30d{oDOG z=AssKN}TVUkH5~$T-R22{pg_LEefu?AB7Xr0as{$$_C37sPB5X?}ChTRli#(*yy0h zB1X;~yDH8V(ru6Lv4aEpjD}UIS_m@dg3d0szfej0PK>ZCf$Yj>UK0-NRtXVoR8dF9 zw9^3(&k*B@7lY2;8bnz$5f#zI4iIE}lEO>C8~)Z2sJANod$WD%a2pl0X7eet={v$5 zZ-!oBC!Uwjj!y_9Iw&-ZOKViVNN$vjH2d6iEnv1+7cgQnMSe`{P#XJ#SKq6$v&FwY z45V1u^^pJs0?p9k47A{e`8#=!t}6*YGVgP;B+$X7{Y41F)M0RJc{N9Qb$9_#Q~-%j z`J5S1ZhV%Z#2a>F${{)eYvERK@ayqzO&Ugb(1Nm#lc%lBeXYUP*83S+rF#5;6)dI$ zjmZ6QD@QP^dsZ|emo*tXKi;5X!~}J*B;E&}6qsUbE-O73Wf_}$SO(cc%2Y^8B`Ka4 z9MPFCW&pV%*h7!uUD%GfN)F@aTYUtgVhw^4{V>A)-g%%%vLxcvFM&o%gn5KDyJ~7+ zp~hbtT7>8}=<)9RnIJe)EQ8+Ii;F$TG8RUN+j>o(3WR(`L!co%Z6SAqZkigy+=(H4 zJ^b?g+*sI=-^yl8ZLAUmjS1Vn8K6@Q19s2Oiy4R2) zp&uin6HXRRo}TMF0;^TDlLligsw_-8w4$P~xNJZ7-rE?671`b2XIg1!Op@PaAHNjX zwaiDZ!AZpZ1+?d~FYDQo>eSs>O?kh&tM>iZ_V$l2Lr}USXCgm0la$~PDKz(IfYe_8 zGd?YyQ_bpC?7<#x`_`G#Z39@!4Xv)--8!8Fs;BD==PR1N6O%%!H3nSNQg7k17_;6Y zI&NL|;?-+iU9IwW^NrxW8E5HR4WnxSh_u>%ke;2NcOm`q!>B&y#{$BI!p@3ciRGd6 zmNp}MifL&OG%H0e=F}6r>sDb6=bqkczN1NyQ4-$m{F@ziM6PStsA4NT&;(8jG4p0_!Hm>iHnIF1&yR zY8o|LBvSnPgI)vN_LH>gg-oFjx9rnwyz`^!L)K+qO-Jiyr2F0szt2h%MHg_0?q@gx zw08mO(V$h59kA0yFZZn;f3+m@VW`6V=WK%slrgYl#npS%%+K+d_DLuxvArRol9{$0 z<&8tl#Tro~%r{G#P>)np5n(0Kfn)}Etg2Rfv=43clsa=s zlY(!Tc<=c&Ij{>AGBSlK?PCru(#k$(5Eb!wb^jMTAR_G3h}hTVbmRj_-0bYiFlSzF z-eXMMt7to!YN)njFwcm2ypn^(c49)?jHJg(-_;$inNoUiGM##1i1=JL4T>}Hhko<(}zXYiJ!^tg({-C*K1Tel&Z6AqDuB{eYIU3Vrj5;W5V}hAGyL2&N#Be z2e~bb15ZJj=1M4&cvv~8E7iG*$xYe}MBD!A_QNP^xaauEh%0d-O^!FYiOYnjA(tz# zJV;*eLzos+BbBbyl&+{0xe9Rq-m$l;_qoR-X8Hq~sygA;QgLf6cEWz*M*gqmME0U-MN5;>-?`YsxM>TT~YO{LqKa%_H}qmVoSepyVqWXyN?XG(2CJ zL8=64T7@G`?6^5!re>K(o$Xv>R#VYkeEp>~qwXd-=EJ_S9r5lj_b7b~#)UnAySn_A zN8Qv{x*ubA%$MZydtDXLg9Py_fuF3WC$PumuZdFnm- ziIkKy-u!i82P)G9E{8_hf;a(G8=+kv-JWZFjvc^{db$@OWl|<7=Y#I~lYHDPknMpA> z!(C5$m5#I2IsOH$C>lvDHI&ADW5lrpoc1V>RT}I zi(KLscMPhP!P&P`S_ORW5_4R`Hs1&EHr)bZN zBD|Oq%r_1E75A}(2qWZd)jkQkYXi4^-qNK2k!vYJyolw2oSk3NNa9U&0NPNGq1FZE z*z;=QmCK9ocR~0|v(*lh?;IXobEwW24F&Hb$upf^z>;@fq}By5Lt`3+4*hk<#No%z zYAkJv)mfs>FE65ttQIdDd7L>6LLC#!E0=vR`W&1acuoIGeQQYy4Jyv4W^h1s4qQ)y zyXK1;2N4;w2XQNETA{XYFSPpH5d%I=Q&CYZ@9ZTGQIB|w#XnmkW2`|2F2MW8T4hiu zqJ=9b4|`L7dFi0NGR-Myo80#>Pxmgkh6vJf=1r~>u}GJ;n;Jqwnr^JIojRQcwN5zW zDYCzRWMpYuL-app+^f0e%va2~MIEv5t-S&)7&w2;-aZ}^f5o1vq5BxOHK*k)F$%G) zDbBMn88fd?FV0NA{WeuG!2c_3n0VSpvOt!6CGU5NI?N&R&W^AL?^A#BCkC{FA1gw6 z{`6&tOgy7%VHpwov#sk}Fwgl{*xql9n@IaP=P|O(VQI;|y6rzrAe3kk*6r^`Xdd!+ z$Jg8pAB9rM)luXr?R~Lg5C8_Rd$IGBiQUkrKZW6^Rn@~?qi$+E`_X8M+guQ9OnYR{ z?Jkfv{pGS@cd*D6={~?sl@ed{VmZTiYbQscnflDl?b`d$SsJ7W=v${7DMYBJ|CyZC z1y=@nwq`P?=ZXu}Y`V^lw2FdOycJ4=`h$e*RG|KqCq4)AmCL_V`2GasSEC}d#OMzE zUvO_}a2>P4G6*XsTk`!DM8=lU+OIWA9T~H@OE$(jBdH03k0(@hg}v1PPl;%bSl#G? z-*F4qzsQ&(^cb*7N<3Jko|B@wR#Oa|%(YKuW5n15sAauEMy)T57x7ju%dSMzZB>NRmOP3k3@XN{#XU8v;9 z8#U=QLfnJWEHr&%1)Nt%xDnfrA1kw+76f6A7?@mWnC$<2m#V7qX1rY5o>>sUOMUwz z`crh$H~CQfu4A=Qu5U<_x*rBI!3nWXdH8%btL^pZ?adedW3m-JD$rcfAw;fYgw7gp7Um(GTU%aEb?N*aO`NxU7saZ7br zf+0){>>jj2f!4bCoTxA(^Vg=Bqy35(Jv{NW#@_CJBgt_qKJ)^`I7%X8Z7UqcP_W2W z3{u!iqVf!Hi67}}3WCUSbPB zTyz60(H>urU)(ms#Tzt#^k|;OT@#?BAZCO8Y&u8Y@(zI$pHEyIQDl63Gw>kW9(tiL zpl=D7_CC#)bF-%Wl1aBi5GG;lIOMy(ba(V{^ZcShE&tcC>y~I8(DvQ(rJ(uzZD}Et z0Ns8w>RJMKaQ5=8_ef0gzC;=l{|102AF%Nna9CuH7sOF9UZ}Itk?CV`O?FRH$L->2 zS^kr|y$P-zqI;aeR%g4^_$p1YEw#7)#N+8=^3KPN$%Ft@duBP_CRZ;FW?CLdn~@i|7M^OKO~fJ>!E#Cs-)wVMttT zt$zj#6|O6>tAk(R)_Bg&eW$GtOkFWZGbynfbW<$gve3ISBmgDqxo~`~?^6RfATu5D zDTa`^`g3``RkF%eZ@^B{+r`0ec!TdLoWZ*0b2d<5Q>tIZ)8cutn+P|SqThLu1Iuna ziV?h(Nl$qR%a8dT+4|6K3zz*P>8H2Ux9(3JSz>NHDd=kspYWgMtk4dAOqZwe!anJ) z@omeHxoDLT5joe3+*|)CLgp-3ORzIGJ)^u*YZ$x31C%1FgJ_m&H{e|O(xwYUoxad( zd&Gyt?#9#MqFQKAp`=E#m7pk`BYoeSN>lD|2c`<_eyRu9?;d4FY{G$Lq=?}2M^Qt> z4hT&i*Cog=m?m{LxCR_L2RX%V{88^@{K`UH(H( zanFMjHr~84|C%d1W0*%uI7U(+=Y!#{^vPk;UZ7pt!8y%;y$mkF6zEr{&Gh(*quOJE z>>K$56!W2Zac=a!N0nW1M<2NPaz#PzW3qek0SgVHVM6F~bdf2P#Rs3P&xsm_=)01B4)Vl2v%5-P^wN~h85fyy z5sDiyZ9Tq@jS*q|@K#%#zfpt5IWdrLxV6$>(P@_H#>c?We?|udDUjxI<{s#Wfi=o- zB;bR5F&vOa9HWuFZlwEA4<6H;b?JMndx3_n1 za7}ZmOE@aOxwP1}PuT9Dzrp7N8^Ql*DX$;c#R%>)#;iS0Mfsdqf$2g+XDFaEl*q`K ztT&(0b8*f8wEvs2XKv_NGvH=ONHH9_V}87+W#ca;zJ2g_&Cbc8A`3tb2}D9`QqSTt z*+!VS<~k^w2qfZiiokA5wLty?0&w{y=V^hRbWpho{JA?)R10r4%XC{fzR53rJfd~G zdXjo*3pi-;riRss!l%?M02F5!hu3{Ej`JD@2XtE!V45dNG7+LdUqlZjuu&XEw*Ob& zCOudcTQ)Z$&G?&EWbXSKycsv?LaIuSv#rGT##RJBG7=J^`%{S+LCHfr3|x~x^{H|l zDGNV?)ajV%+DSrkQxKZh0X&=M(SW7Ec7^ghx?@-rI7G1gr-JsJBW22 zRFa|hcu!p@)hh7<%axk@GL#HfX@VRQFBYZdSE>Cwq{@xlOdpsU*y@N|kHm%y7ca_0 z78y3Q>G)lp1yMHlt8|eio__*m=Y#Ug&q{e;=^dSLnj>%YTK)ahwm?S<6=xGL^DJ@x zU<7cQ;kPmdpV2v)Vak~NR}~>=5U)-np2mha4NL%h4yO&A{W?vWO|saSMPR|J3gOtA&a2|5{88+vor9#l-(FC9ojOjTU9h z{a$$F>)pzc zt>k3fX1|h0{?YT=8D*BNec?D6d*QqevWKTqCLJsxHXg6|w!aDBR|bI-@j(*PhcAD& z(crBjo(3pe^#~M(6>5uYHY$5~5P3(veupCiLl z#>;2FmVAxXzGsC$b(uQQvk>@gYQofunx7ZEt>Mz6=PRY@k#^ZEb`CE`3Po0qJ_~R~XydjNFZWJ~sfhcya9Zl|C084cFqx+~ZpY{W2 zTOTgUw&H6_pSw7rI_F0hO{Yb@ukCRioicGy|<}Ghdyw)?S{6J=j~3H^9b$4 zfyH8r1YSNzZGX3oI+l!X0cb3x5Sp|b1b>^wNvIt{;ujk;X{gS9Hy_$G#0_-utm`>@ zn>3$&R1_PJVWe5)q7ppK@(T0wS8jPx>c}(@-Vdl+o^2eky6ZZrIZq?Hy^_38eQ*@q zC$03fzU-5kn5no)m2n>k3q*jTa_84~6$zSM_T>cr19Q_@%5K!O*U+G7#x5AK1+wQL zg)*X!eNz7{MBGs%2!Kv8kI&T+22fZ@-{a*%pQ@!UNw>Sz>+-VpZ-P}D>CLm$%x^$v zjqHL!c$K{a(+Xp+{b9?Pw#4EoWg<^`Rh;ofUzG1)>*nhDz9d%}G~-n2&?Ub+zFR)~ z6bNcq9d5hZfDTvj{|6A7@^7Xq8Tim=S2DfJh2g{esr%5BlBO>!_GA6Cur6%2iD3nQ z*ZPJXyTAo+=1xw;i$LH0-72C0Taj9;Df3)jcmbXD4R#7`Y$MKuAyRyALi;i~MnZg_ zH)W7J7YPxKvUlsdv-!&XP_jNVeBtZXr-yR5Mv`~PQzfx8p$->qUd!Yhf1Q9mIDj)8 zRs-V9`fz(YX=Zz)IoIonf;a?cCZhO5^UBPDlKtxJflYJ_6)6F{8$*O+(bAb59P1JXtD=j#_)5>$aF7aXcHg#W9+mA}0F(Cn9BieJ zM6PpaVgI_CyL4&GQ_cvr@}m(vc>8aCA@7#^|I){b&lLR5R%1Mjwn%?iPN!I1{9-m= zV#P!bqKuafA8`%~m!j-kzZi5duT0px6_Pp|Zz(;ZX zg4}cT=&x)6o;~j|ugCi`mNLznL^!NbUgz~>qjqu`x7Tqg0xTmA}3G zUnE^+K$~5&#I;D#;w>$1#obDAch}+)Ah<(uC{SF2dvFiIDFuqVySuyJyx;wuA5V_$ z+1Z_$6Yl;@H2Ps0YaHNvYJhZOmb+Tz>;@c2g@34vimgs!l9GW@xsK5NDRq+H|tqdu0Jq&qL+Q;n9-G>E0w?VQ_`oQ-amR7m|3Sa2)*>CkuzOn-=w#U=m}t3bl@i~uSflX*1N-|!KPd*jLG^e z(lGGQcjhE`R&Dy|cm*ZB#1K-yPR}>&#>k zm21039aT5$?5N~CqO_b!V~fhtEsj~G|7SO`SnBb1;d5Is(N5v%XB1$3)}ixd)2P1A zhuSpK07)W7LVIur3^)xFV<#K5LYy{BVNf5!AOE%=7CWk87^@h!he54RuehWv5gyIF zpV*F=?`R@3I%d@AT{Pe572NE#M!9jtV^=oJKpZ*7``?w^C?0TqD!O8m_9zdx2%0E0 z-g1t%zB?q$uP>1J0%tu~Xn`6v(d;sT!bR#Xs4{GgMgV$0jjhARW!BT1Yg9w;-ZdCp zCCe9Q37jhKmF4w#`@K&^N!b2^m>>9}<>3+K){I)!VICe4^0VFz@gDW>e=zH0^6aI% z)kmyJKd7FVB&=iXgr~JtU=k?8r90u@8DQ3y7-*0O79EuVaAONk7;u)3k&pl}QdtL+ z%lR_ELk8{dZ|f1BRS>GBPw&5%{9@jhZ*m}j7zGqH21c|< zMF-tX-=#$l;`aY%X)&ba7|`$NgV?=J5jdx4Jzjm)yrQl8TSs~_YByHx3$N}E>wPRH z-7r^2yT(PN+!N=0sjsl1b`*kIwTMH3nyj_5fYUw~`@^t(iVt{*5(S$Y#yR6A&eeyf z1Z1;S1aqgWG=Ie3$j7rvG#vzTEbjRJTh9@&$MdY<-Q$ah&cvp7;O$|oIko*rcvG>y zqRtPbeYxJcT=emnATyVqW~gi|#I|jI$c$7X~i8ck`ZFnWq%LukxNy!jeI) z>g4ktGy--PUV8nN@Z6|b|0R*PB0~p>3tt(#%kh)}EAa z@L89I|3Kk^p>jV=27qz{{j@Ydzp6@cjfMSpt*G!+g3;r)A zBx~e@7R~~l&-TO!w#fJ>Zb#f3yR0_U1^LjD6GWy~AXR?eA&>3W2ze zI`CIIT#TC9RUY_#s=P9fB>{-8)avJ@E@IkA^A1_tV`35*$0yIl~*_uEWpUa%<&5#yb$oy&ro6HK#=f}p- z?sYN+#-9VV8?CVbQthbV+9#WGE5+F7zUS&xYGu{rI`BgBme=YLJa|-i8`iA|r$^^l zaaE?Gw73UH3}?-`?yPy}>VI+DT6-$QN~%AO#ZNdN{1+6}mmi+}sNY8`h_5}Hv^T(8 z>xq*O;s<@-z$3}7*3AoL-?za?2+mu@iWyANEeNBf{@^L#qsvd!#|T_L!B z_277o`aD;X0L~tDRdX$xqNa>iGABsH5)NE`H-0;)Wrd`T;s%0qfI=DKyv13wRSC8p z{xrOlUeOdl2S!#z>2vB=mQinwK0S-9{ap8Q?+1@D=h~m%ou~bGLToz?1YtB8=-J|t zkFfgZ{;V|@5Jo%ElVv?+O>*M^_8V%nTR>qi^y-@Nh*I8Kh`F&({djmtg3>-@&~DJ>Z7U9TzrTx!r<;=PNf4Z zx^Npmur0bd&k_1mwFu*)iLenx8-WzQKqh_nPe&VmZQpv*BKFerNA98#fg-j6mnM>ikTOHjXakPs?UiiD~E_-B2(XXuu`uT+we zT@=b`^AWKK=_c{KU|-UXycaF*48Jb&b0tV z#+lky&a9|-5C{lFeeOUP6Zi~u5L4Fo24btF4Wa)J2mlzwS>__Dj`DRTD|$IOpf=rp z#Q@mY*VZHWRzyEuhUV`_^utj6C)TxEMHDPc&L4jytolxDFiZDPmq}K6tItHF5eGAZ z7QGx{<-ubMy2q1#fPmA{?2O&)@&}mBijqg+-52$X(NR}D+6pveL{3`fTKJQ91fXi! z{#+wH73GTehTuUOdYc$)405~w5;z)`eN+COD4-a| z-r3d#I~ruT`JcK{)qE^jyK<$)pZjD59<_8aKeLS?K*z=-V%f~*f(fdN|7(0X znR+H)G6nf-b|DNlKa3h3jm;e%puon=856V#9Z9M&@7NlDcgQa;%ravgz3u8UR7kUs zISzL$6%%9e*Cqo;n2Q<3M6sk0BuLv-WgPzW$rXhNkZ2hR18XWSg8o|Y4vs6>GU^wl zn*cbE&lNFYogiVlX%{o1Qqa&wSaWXB%MM$B+uQxU=i&Z@6;5G7BrGZ(Fy&8JOCw;p zd^Cv>#}9OvbLY{|tjRA2GB4_ViYzvVbz?T%b3SQ|qN$ZP%Eawi&()=Y!3>2`i9cT8cttPNRy;$&n4&=14DsJP^fbDX==G? zJaQIFZgq=!>Sbw~%T4X*Fq(5C;{P^rVql$@?({L9BX91CBC~ZI;-rS}=;X=@h3{PJ zYNOjw3X7q2kHO2!%L6{0l6fpSf5Ow_T&IYX=oE@48K)30*)k}|UnzI%!`?}(sP?*l zH^S2v)zgx+=ye_rRw3TS48csh+Z9T!<52R+7Q zqzy^wqdoC6THJH_4^z`DEoLJI<@j{?S#YFBNn)wJi1a!tC1bhl&QDh3aw@!jYq|(G z3NEE%1k=Eacj*n^K~65(M^keVlq4jnO!`>w;&cF~-wj$?JOHbF@d)mZj;<&T%#U;e zsZ`J8X!Q72bqQKbh=W9%ui2mFd`W&bYGzY?_*xIYIQ91@0LA((S=%Vx_r%V))oU!9 ziC6q+u0nT;bLmbi*PaK|Ed?WllT2|{0w1rz8@7#Q!q?+H!S}6?7jbVA#)yBdezqOV zk0Re8L${KPU^2g|qZ8yO43@q!((^$bW7z@;h?s z(HS}xh*_&-=iW(0v7NOyd6Z4b9n0blz3+9P&>h!u1M%H`fJUtjUi*s#klEYB`UA!1 z99S^>dwX+~;aknmyEuwAjfIlK|0E)$Buu3;hWa=(Mbk0wNm@NOg|;tsw7^o_(&#q= z=XX>IabIzpljk*bUnnAj*e|s3WAWQ+|FjU`(pTTSds;CxG&D76nQw91#wt2np*y;L zoO?@HJ}97ip{FR}=i}r1uI21_W}{~}%jf_6H?Zocv7ge%6*nx!{cgnrN9gIeKABc* zj^g92B-zTv{{8R}g>m&!bc+))SIrO{9gB^OO2d*826?%5xz>3a7U@isD%&6;(>!8C z_(k^pA>k#|P_XIW-hp$YT(_0mf!5qPKS|)1#1XmDOum_t1V9p`T z%)K56&ne~i_0I3!1Ij1w}2 zD?tqihf|dkU6_gg?&ucWFgc(HDG7L=G@wQ`WWKZYn)g1iT}(^#b#~gHyKA1VGi%oi z^~L`^I%*}_p3Q6_ziHD$d!pE#V)I&MC*ATX=dYq17#wqTnc5_2qy?PjZLo`ZtHes; z#QciV5Mni4X>GDk*>hk&w3h$WQDxc8hG=+5oz z%v+yLwsG7D|8fiI^m_?HMCF0GCHeI%HrmZeQpeY{nA+aIq#U-eH-|{u?IE*{%o_A= zIvX=*qq%uhM{K2>ulJpgZpWhjtJhSV3S;qTbAOx$$Lm!V!w#!NFw%>%?{C2G4 z@|ZQs6(;GtIsBYS{KVqKrM(&u9~fQEpceyJAu(((=D*<8-E;Fcs1$h4PMk{rhv9*k zfi&tg&bBFBZgG7<%AmOw5_f!?3Y zzG;R(S=osT%^55NKLbRntuUq!YRRRwyPeH@TlXfK8%d{UNhiXNyL4pbFZ1DacwJl) zZ+tO+!`hzjyT#zNTn#D|JR-6{Jej60vy^up^&9Jb>a_;2iIejM7Uu%mj8h3~CMr-7 zIzmw*yfV5LV1fdK&bOz48n>}*VKN9{r3pLm6dj4#4wW|6a^DiHHFdSuZ8NcV(c|tV zB%*9(<$(rKbafbO(exaIwf4tSJy(wGJ)YO(J)XzM*KtqT>LCzEv;a~%C332U<%>A1 zKM~dbS0E3ZE^p&f!4Bt~7RHtzcAeS+k!;o`Qy)sJK$H2jLwYSeV0tGI_PE^qVjw^# z;?GLje8Ips!o)S3N<%|PjtdXPW;jis$he(5NBxS!;nShTWrCN;77^fGNL1>DR9ut0RvjtLdZV_`WG zW8&#O0(1*@&{Krnk9A&`)*fd|5eG-r)NjCLM>-C!Amj0_CIpV*$bOC==?I74vmH_G z-;s%oQsrU+5z?;Q=*Fk*Mt>QehS?~p!%4Dwkhzbx{<-j?+%da@?~2sXK)9aFTKY0x zlw1W&A&#<8t3|spUI|xmdfNfuMhU+ z%V&w=-VM3PFN*p(uI>61>3v~xEzlj(iKK?VdY>~Y4y9C)U@83XWf)y8`U|tQ+>_Tp z6H6ih;1c8|oq{`E8Oj}}4ndgS%MV?NqCb@>WylpadsVOal6!Z$lIp!vZq$A-ImC*b z!&|A}Xgd>yF0sNT2X`F-nBL>AB|A}%lG+5DgLhzm)db@!)l(~M8>?4CF0lI_w{ zJlXT6<$)w`O8J&pK1B9CPv|g@&&;%AuSIJCTLg`ed9~|u1?P>rEt|EcT;CbN)$pM%*W^S?6yy70-#9b?<24AE50>Kl!#GiC75#eDhl(LH;#&X+G*uI>>O`n-CqUIZn{O03oy zf3y&~P^J5}4X$eys=DRw)$FCGGe&o(BQ{m($U_a#R;q*Dr^MP(G6W=y77ypYtA&BA~t37kf7EmmzEZ(sP(b_kR>yJ&bE@ULJ4k)PD;Y3M_`>nN0A?C!*^w zWr^u0TsFfSr!t|*(2iq&M@z%|lQN_Cu^7R#V6fclI{=F_{I<~_+xweIeXh#}a6*Ac zAiH#J3;Oru%02%o5IdUv7l&T?K?(6xJ1?_*`ly&`*#>=kHES;g@n|P6E}GPHhZr+a zOjWN@5XBNi8nIgp$-#%vjVUHJ#*f5Na1*Y{YXfm%oeixTtK}_fow-R6-ACA26&B_*bct@0zLG6-r6`Fh_Z8sJLbWSr6mzSN zQj(1rQ)^iwKfwv(4BcOM{dQGc`f+f(5iX&HF{hJMTodn729=IPID15u+Rn6Q++N5H zp)r?qG^n?vR(?ua`^%4f*bD6(D_kX4ai^^UUS(#6JB&Kl|E1n|D86^q8&@pRX}{F0 z*1P#h+~#y7+3H?-cA(qJh@q~WKE^FpssrXhAuD$j)wMvSz*+akRyHlla z8BM?svq|D(t`r4%Vjp)f9}D*R*Qb3B13jzsW@7VB$T35I+Sr${c-Po7A8WM^DInX> zooA_daQj;!wiV5s20nfFSX7eS%QysPPl{WIKp*{|q-xcxNMp3T5tmQEw&q3s1*RLY z!oOgX0%X$0n_Tw*MzlZ)!HS6;D!s#}3oq&w$sWls`26J`LqrnLHLmAp2@U;}ZOndd z)O61Ku-!iaUh+GyM$DyqoH(snxgEa|1}zcYdfNSzoLik)ZoYHX%@gEwpb$gePC6fA zv3QTyI-Q4RD}`ZFOG63qAD6%OEsFsAy}+W3obD`&i>G#Bqc|UAN)KHkc4m2f|bz;+Y@AGuoxFJG>`yTT=c@O9ip_6Wv%gqZ=VTm(mDtz?M{q&WM`ME(;71fD)qFpBB@8Bc8h#^o*me7EczxNKPIhP0?6t<3sHnc27>zqhcG$h3O_+Y4RMit7y z5y(cO{QVsQk$?-n;QSEGzA`n{(RP>Yb>?ToSl?nGs4(Wb^D$nxxV)~6qjT*pAjKt3 zv2D%1wuOZ@F^l|)-V9h|#{OOW@O}=xen5jeeMA$p>fYB;w@IT6A9C0N@9y`-g(-Q( z_SgDh_UD-x?8m)Q(TrE&MrzbUKQ}kCh2(M&S_B1>mvhX|?!$G1fOm%{-f2fU@?+|R zP`#++LGMFN0pW19X?pt8qJp!ogR4|il$q<|c+=hJQ#28#in2L`(lybmyAwQ>*#SG2 zKi4a8v&YN5E>m0X`f3MUsCHjCD$#R^^*5(BuO5!yrp{i*Krr{|ju`zWdx~BQ+-h-g zp2NroxgyfBy=XS83);+V^gAJk`wiNZ)1s5&d5n?5$680BE~+cDFH3RXN{f zT4co~lCfi!R7t0y*d18v^`vlmIJP%U8IQAQ$N6|}#*ErJ*LN^mnGdV{e(>IYB1B(2 z1cSX7YEll3O=@RDZnjEJ`7ZTsv2C+}NY!(8ov>nQJYLSz?aJ26@t#QMMOSxlfj$I4 zK|)DMqOt~8-TLmQdxu6QUd=@Ddn{TE*j*_cZ_>J1iw|#6KpRJIq+{CLGgnZ4f?A2z8AN{ z{{tP_6xiTX2cGB$KWUzFFM`kk+Kln_J6HacVc!yeYIPiIazuU(=OOW7>PXAf1^e42 z2TNWhvtus3)bbqzo>Z(IAQV;uwctoEzt@k@0<|-K%ym|y#%pjsE;eJ-KuDh9vyxJS z(#Pg;A3WMrLlG=5kQrOM{Xf&JM*egxou>is43EOyw!CkTp9GY232GiN*4U(w$I^TX zu?sLZuJkq_vvIXTN^10hhN^e6mTHeauk1(Kb$zM2lQ{ZR84i@Vx;;NKU?fp+cCbej)VSviL}?Hce^w zGO-BA&|MHkq<~OnU2hE$EW2;V&6O1Yz40rq*3V+|<3ZmN23M(XlRrU|ty-0O9=`gN z)8#YrIpa)yDJ36w^!Zci&-C$$4=qS$b>bGZsjPX>zeC6sXF!!5KG&@M#VEw^)Slm4preweae7j3HSkJL^`&i2*;WzPL00O2~!{{q{ndgh9h;pXvaQM`Q>N9p~xhEmdCR0?Bq zO7Lyg2bI}((>Hq{RxE!>*v|-qixgc?2ZM)Q1XX2r^%(o2FCGC+JFibv^Xx6ATgBbo zL8_pU(aVVP1fB1{i#Z>X+0SO#;=e!kQZqfa4?31v- z2X?Whx5ba4S(o?Pxi1(d&|`RNYFvfdbEFO_6JYf6#m3NJe$)F3`d+@$x2{m^s3|^` z@;oI#$O5*)04dimc`N4yzE8UQluZM<*$q9?qsSA;b@)UzKvBQHK zEgEzxD$XND0Lf&|I?U^5;+|VuNN$%96+kVjroahEW&rZj29@@*sO*tu^soME$=m3^ z>n!!lR`l6Et=e*{S0fky!{Sn-N2?Q*BsVxa@C8#<&>sc`{N?(9&V%Z=U3m|Og|>qp zR>&Ui>RykXEPd*bSD}o6F|!4sSbcG>t+D0Jy=tf|(~WNFFH7ny;dGfES&d9U_(%&0 z1Q9K_6&L7-MmtziHJ)-=>`WK5MEx^%c!FR!rTb;s*55_}>$5M(ZzPQ#AxG2&X54ji zd1NcRkibidg&As=;Y?OpWFZko-vnljK~p4Pn+9Y5Un2f>Ouq>N=00G7tg3o}DqHNC zfB4XWxVQd+G-Ik9DXw+N+?UGp}7O0PV;_j0?&4nDD&S3Iu>M4KWj2^M24`IOM zy@O?U*IK12&Z!%+{fK>W)u2LD<`dq~)|a#)%|5i_YlodHFD>5?_2GjyR>q~Uj}XRl z+B8rSPr3%yu=u$Yq0%|6+o?13%A( zyh=pWg_e=vbc2lwq1y;3uqeT3@VoNNvMIXir} z;b^h?fITafILcq=W477|Kt0s9p1-Ux)5e(F1yey0zlJ0i{9M0r2aepwmVOtX5ORO` zs5Q@MpPXrlzKv#msr=8yP({x^11(C&b!+U5CV5bod-o(>2s$7%z9n3sEIEZOq2wQN=Y) z_BKVX*PbIMORl8OG(+h9?}0aamyfx~B;y2Mi@z-v!e=;(|J2i#&a{Y-r?g`xO3X1g z*W`(mKSG(08J&{yw^uK?5B@?MZe z=l2iZTzhWuO8+Q*d%QW5%G3G=nONH|&V)wnV;h?Kd8JmfqYyR4$b^3z%?eSK}zjq>@{f1WT0 zzSCw8>{2&NIk&OlCyiKQep@tfK(e;n-L>H4t&Z7~8&7M#S4BvZ*+wewRnVMG0|&TU zS^~T+AFW_diA*?`+SPmYGuc1~tN&q=G;!eYQu|A8%o)z4?`9m< zS<%%fWb<1Sdi#24Q8AQWd{@xLg1$B}9zsc_U^Ubwo%of#=y8UB7e94Pe$>zvYM_vZ z>TImO*W8(5d2xezSo8=^wPrj;N#Z`Y@i=d-h;cDGvSF@ekq-Sj_}3PLJHfh5`%e&! zEVf{Rojj*?y^5*oq#EYDKUiazeoBVAS>Dwv`q&>y$dLncn&hu}qh1ytwr*(@$v4Rw zU1x14>T`a^QXO~>G>+0l$>wjG!({~AvG`#y?tgw)#m@*14O~9eWCU_!#0NEnbI3PI zbq^_beF7F{zo9SphVCV(bZiJC+87VMv{_2Zh4l+xq13p8gb4LL2Zhs2DAwY#t!7W$ zVw{AEPZ=|MSCVWUdFisb%Z_%xX>oRdy1r-a@_D>*lx@`IQaV?A)n*VoknA&S^l(DrgqG6?Xux6n_jV?DEnPQog%o@8oB2<*1)}+1573 z$u*L_x>IYPR+H~-n}j!u_Io`ch9R7@IusSNhz@hYa{3%1ZD@$o$p`ew#(5kRXZM^- zLi#wHG1b6A5!OpveDq~=^z~3PeriNRt;51)iy7%!<+jjVK>dONm&&Jq-aAo7P4Cq_ zrZd0!tJy7yD$c8yj!rEg4Ts{lTu5lh#Q?|WmM{UlU>Ca6r|zShuU%8Gm2fwBV=tPh zH)<*@+%>y_(^&NSJf(u^_HbPG7CxBWin{y_3Ap84BFlQTY2=7ZB)b#|@SD@)(md(q z7R@xsgWSJ|;^s`MP7sv_EBAfWwH{|vyg{_G%BLZiq*=#$cKKz5KsR+zu`GGjkE^Vb1ckfB2>lx zSdhdNy%1LobGoz7{?;LLnJT}LFkoJSKcG?g6CYbsvekf9%TR&a4U<+^5~N_MD#FUo ztH;kXkegpFZ-;qWqmStMoB5A0X{5Qa-@!b0$n`S*+A~TocByd9Ok<&#erIJBSt4I@ zcNJSwetIwAZ?$m}w~9#=t;U^QJS}X#V2~rV zbz8T*i+~gc)$9t!7O005UW=l>;H^kaE;UqPx=(cMY zQ2FpQiE}T!K?B|DtT>Hv8yw815=o@l8BPq?1<&; z@MrA7VZET@cNdEtg(rK!18fv*fND)gBEq1+e#U|;tIHm0Qls}N9^#-tpaA5yk4#U- zOxtk)exL49$dQlEt=OI1+PnyGUBF~A2P^U$ijfDATo-#hh&09F`+eux@xY|im{b`h z<^XGW~!4?PeEQwUXN;~$NF&|Z}NTb_?N z(nmUuy>6NC){dT19of$n*Y~4bQD(36Hetf0+c{dTGu{3{nc5SM_rVPauJdyJm#kpr zVMLMRODW#}XkQ@L)M{`lt}#c@%Sxj@@bIk`x9=>fT0^dF3P9z#n@-jKvefw^NacUG zSp-o(u%EVsr2cC=IXW=)n$QAOQlm!K{-i~-Ni0lx)u=3CESLydA{k&Gxx1i4S-fg?x5USgHKTG9%&5Lq3NMtf@wweP5%G^RcZ;0bEGv2SCx) z)Z~mCr53BI$h_cYAeZ==F^JO3BLM|%4^_Rr(+lCJW0)wu6e%C=a}g-Ll2hWX4l)=b z(f+Bgb4lt*p2nZGSiHqZR3F_KT#w@?>lybQhwA4cmRK{7xg>&o7v?u8619l`$E6MuhaNznWC?wwHm0!r?wQOWMFF>T7tw3-JeIPZX`5Sj>@txnYx^A=z75|_}nWD z&D@<$wGYy;SBvQR`FqQ|O$r|?xOF)1Tpjc|Aw&IrM+jQ;+ac8quI&QaKA}@(-Z%I> zWDR&I1}pEf;K|}zOAS8SK@(>OL7m!P6e`J7X1mYko+*kNRSqihWDN|kxo3r@_#Iqa zXt$sLhM%qYzU>_=k4-WxD^s~GqjZN(Z})bZNB=g@fr?SC3fKBP?isKV9qAR6$n zq<)s!&Jj&5d0(*h$P1$Mb9zgWpQEeN^K`BobV+t)9C`c(Vo9NP%z^D?FZ?)v zq_{7aQETUo+MJ=5mCa4JlinB8yEJByo$9k_X?eS;aypNjtQiNA1axw#Kzv>w zF<|5|gZ6amk>vd%`8?ZAFU+5eI1p#63U`?K+pZ!)XZr1$fEz<>nfGRGIQD4#Bi4P} z0vIYoB{H2ll=|2Q7%dcaGT{|+wXUAoa2+n@gkDauwU0>Rmrl6wJM--XS(_eyybIBvE~y zsHASWk<+u!D>T(ZJt(5QY|)sP7TOssS8cTDR~-x5zI-njB0d=Zw~vy5f>X^HwgdOPC|!0nTGT^O?B63KBO6TchZg0B`a30n2WGH@ zi<_WI^;KXU50#F5fPuu0tmxH&vPw7@)U2jSeYeY^-}YbCpM;dODK0j45j8xFoHA}s zfJ>|xj}k5Nlr!@<5%JWI%F&T&+-?%~_bs-GajS!SzR z5w`iDr{)D8MVna%5d=VvA^Omhsw{HRlN$DZQ6!}4E>FVec5}@kU}t3N4Z|#QFfcmQ zOgt&NjeY9#kQMPm5tNwOGO_o}4M&fqPW17zC>rb$+b(=jS(1&GBkIA5nI-84xsfJ= zYsZi$o|h9N+P|zZeUH)VwN4^w;KBkEBB9Ua_v)q#hq$M2HMZHe|Ct5#b5HSc@x-{O zpgL%3&ZYNp>$v2$_sI>T%<+~AYIdLGEnv~xVcEVcKWS|&s-UaZPBS96TZ)POUZ;y*U`WujB#M1iZmluK;)*7TXqfE)RMB@Ct5 zxzS0^WvV%|#HcdmzZRUsVF^IU>Nin_HLPy$5YV}MHcvV&5ZP{tRKn3koV>)<^*f4_ z=|>gDaoCcT|25YP;qKc_>)HkxT$cE2#}kzkYOwgxYU?SG-;nhxmO^mZvR-n&!6vca zzE%eX1;v|;B26b*PQ&Y6f0-OqZ6u1>$@0O37Zbsrl0ru4Zt-Hs<7|1m{Vh8Shjwa# zqSkzO*YD|kJ<}aV8uj5KP_{7RykdW=Vkrpjyd1#6QW%wtRUQp7*h;)|B_{)h=JRIA zWMsLEG9LR9nww3T;?+q0C@Bn$9^1OS^UNf(7JVP5ZREWH{d-j1k#K+N(Lv$39>>bf z{@=D63(1H4{Zo4y#8SrpZ!vJ++Z!phOB2CCCV?^rk4{+l~LRLmdbwnSyzNcNhh@)&R!G1IQm znmE8b%HuO~ACUOVKTc8GN7CTn;FO-b)xA@y4>bUh5G7j^X<+Ku<0n#Bqpom=Cl5_Z1@awLu#c!ZiaerI(FX7UVC(2jZL^%jqRqG9###-#mdQ(5Vv$0^;+D;o}7a^ zZ<$n>lRpx~iqR$px$>Ysdxf3=H&XW7d>ejV)dvT>cq-B^W zy2fotE6#-D;{HQ9-0oeAi7S1VsH>B*NQ#X1RWGpDjK|{H$YXX3_yxBmIUM^eiyNev ze5DQeuHx5Zwk%%3F??JbHwh7`z%IKX7_@gd)8(I#}rfuYpj zgfN}k;!G%F`%2Y>YXqS8yhuU-Iz*qGrE{@#hku(Sf{Sk45)=y8cbeil}Y+=^7^&5UX#&3a!xR|t%9$xEOJ>;K{ zVR<625_fBc5pKB!@P7Zg- zP1oXKK}mciX!8gqsgk+0Q9e=q$f%kG$Tm|=8A-SFScD+Fs-+T+t#BS4)S?3EggcCP z(&4)t=8Si*zM1H!ywg$1S_^o4p-fz0K3;|n_3|Inc2DdPcy=$X%^k@Ut0kM|{q{L& zoLhaJRaegPgW;vb33}DeEg)_1E zzblPh4yn)X@7@)3cJjV1gEQP3Rpl%>Zxwq#D{X@~ZGbGWEjhq&Dc~Y>FCG1rPE_O% zZyM5gSO3{^<50%f-c;u!Q|B|sKd`g;Ei3E9G|Ng=;3-1uz&tzb84Ed=%eGu@}reN(_G{Z2?fjh(q#usav2xA#gLqyesC+ z_T7Pg3lfKQ3nhfoLzq`nNA^AuMXMJg3udcz|CwwX=1cD)p^qjnSt7U6irHd&Z(QUf`y zq{0c{RvWGeP%{Ing(~WR6V*8Zy$^xyakzXV3 zq;O*h@k}-gxqJa)@dOl5anm-P&Os)Zr^fE=xGXraFbO6WphlAn0=nJiSaP}2W!b@( zMcO+E)Vf~l%Qk=Cihwf1``+4sGU&uiz6O2R2*TS)wzR!SR@($GKrK(5Q-;YR7|q}! zETVt(owlrdU{mY@L0fIDL1r}*LYI^*rvtR=i&DE>Q7K!BEH9SJcwdgChOur}_!#+{ z37!_KnStD-yr$VCfWvMFH!5+0G^ujh_xrpEbT69(=&v3VKA%kPv~v0ZiM@}!AEiJz zJdOyebedRXGFcWdm&+Gn^2Rk(941yVjRBn?Zg=8s6GGh%HoF6liEC&YU)@S)D+B7z zV7(dvWu1?FQpcr9SHh$XcWyvg2hY=Ar`_e2LcJZ(o2hl74(?JDH7(Vx>t^Yi#Pv#p zlY3;VkYh?);A@TD)%KIRfq?@xuteYdonz)~N(swzr3Gy+sa)`N>m&m?DPcir_F=PH z!0T~Cz~7^o++EOzuqI!+&1yB+`9!6CW^a#AHu>}ijRTglpGRe$&SW5-OflbiC=!JN zf;}0Zha2NbHtBFUT|^Rv*@DF`#T|s#;4M%2(y9d^UjqIQ(0eTUt~*8X6hVUnRA+~z`I7S&FHcga*5^0+@Anu0*DfAubBxN`!^ zqz<3d4JcbBP&XhzcUe12sZgMSz3B)MV$^^{|VpklcK*+L9v=A*LW z=hb7Bn7O^Trw4jG9&meH%)Cx2-GgeKRC=S~RE=b(Wk5|`8+{+<-AhFEZ3$}Chuvwv z&~8B4^3*rSbyfD-05qAlKcqnVu~NBwUnmiSk-mO<_>0|ds0-{imY3#>sFjk0hOIOW zB}8?mZ9T{t#5p6xIn~e1=}qPfkB3Ek3nt@xiE0CqTEh>?ppfKAfD+IRXz>))j-2_`d8h6vgLt#2@~}0 zvf_~5hMP|ipb6HYc+3?}#DUgiCe4J&fWMcX=S7vB?0#ciU@schw6Jbn)ryw>B!RIK zS*JC@Yp!D>%w>3krod^T`_zTT|xJ4@EZa~>` z%;dK^=uB!b`70NTck1A(nYjtlnS5@;Tr3LVcno6c6s!2Cj)Rm6%X6H;(%4;8vA51Gz{wSlF{f zwc&^I;iS63S3i+SW~hiu0iPDazpt{DO%ibX$iol9y$AL!i)w4Jlh%rxzndkWb1SQ= z-+w>#8VV?D@!M(BqprkW&1Hd^o76Lzy2FVC6iY>e+mHfk&!0X6TX$>+FHZiuzfBj| z9i3zypD0p2tf9kBT}|qQL%TMbwa#2D;Sgy{Inj7S?%R(Rr&7>|$ zqVcn+9lpd`J5hsLSC&E{3;BG08RB{!1e7%cwbTtLTX|3~d{kQGIniAfy^xvv(+Z?N zgn)KM5Ymxk0utGbTxg2MDg-puq6p2+!Lg%n!r4=&;OvQ0V78dyZ@&Du5a{jIms_@U zzt%3W8!kBGbLh>_?Zzod(&Tn)K#P(F-vzMN-{#u4(WJ%DX;5eO$okfe+_G=86 z)p|1@IS0BzDWJexl=IHnR+Kc8d+oDQe)>{Pd5f=-cE^C)@V!e$RnZM7TP5{vXou5v zWG`>&Q&cqbMQ8K*;kj4@0iA?+I;m7%l!I)oBrb4!{?uvMx^o9p(ck*oHy{~}LNFMF zPks7R@ROhXgw>-kv#N|5u51LJ%C&E!DF>5_P*IzFNJikY zY_R&8;h;pO^EH^)qSZl(Bxy!(DSXh(Ot#`-OLby*#ai z>b3-xa#>S`nt!+jgct3{u7kGfD+djtwI&}xrAxWS<6EQ7T%6X(Luswa^1XEcHvpZl z1*AbO_)Tx3S0mFinZ*11tK1d=Ws99aaGmPSZa~>mKwW>ctFAj)^k(iy!-=?^AWeWy z<+6r)6IA?|XetSD zTq*feg|uqsR#h}9=+h@pva#Sx|L|q#RaCTCs#DQQEeRSyDEN#48&?|3D414t4N>W2 zi_7fo{v=&sFJJFTwP^7bF@_hdZ)645Bx^c~C#m$5afxp=#=}z=hx8PU%$xJt(tmT)t7*MO< zVYikzTMTCI5}COX%psO;l?1%4eNO~g6JNXL)%B^bejVZnW2T}%WmM7hcq0|93IHm9 zO!w2{^wt6WepZJi=v`A zX6il#+js5&kCJw!TGkIeMJNHAz?T z@8OSq=C=7_k3Rc!7ufF~lovCG)}otRr*ti4RRX~4qREY>arqdihzqCASR0BqP76Zg znwZ4ZLMogRk*;nH69nBvp$2PCQSKd<`Q;W1*A)cysun(3#A`{-tq|OjKn>l1vQ9x= zcMhy0h25v9Xfks@O3~c&@hA|aX;q8J@H#;A%+x&#Z@zI9P9HxBr%#+hISLC|f+JTi$Fu zS-XQ{#4S)OT0&;-{bc42C*uszCD!7%X69D+&(taCZ~phMK@6d(H+9$i&$_}h94t`L zH|OW!wF?*E()bui3skhop5sSF?hORsgS&Qu&0&XPz5tm-8cMkWlnN#I$0z>2Xj+#kCO?De>abt+g* z@iLmnp>3cUyWK25TaubWyQXw?0cd1K;wnu7zc>Y|tpm{7n#N(_d&{rOty~|S_28%O z;E%ciWw7jkGN}cq@6h-&gPA*$OxR-xX##a7pVJDK=v6c+=(k=!%GC3z0w zuoy5?w*x9gkHOT<>#TYD5vE>6pSf~{sc1_1OsFb)6RPNg`%y*PP?1+)WZO1`bO|z1 zRQl5q-~}s+4?CF5z2NC_K(b^4v(veW+saAO$*Ju~mccSSW{{{Q{poB`B`a?rK#5+~f z9S5r^(5^F$t16pPylj--)%0j#0jv!^ZLV?PnIQnp5O}6+L8r32pk4;3r8*e3_fvNR z%9amm?)L81Wi)da^1~D;mdKMt-+SO55O5sICejcX7-1&v_-q{i2Jyj>x_98lqClA-EyxoDQqZh$iiuS`BUc}# ztO}^GZK%r4W&%8~lv&nQEs%#R;N@9Z!{5|mwL0ZJzgIi|CB0CQU#C(*`GYh^rk6{_ ze6CulR1oAjQLI*TnPf)95Bt#1e>z=fhQF3cB|X#Q*ZiAz?tN`|^R^>LpM6^F<}83|gDXJ2HPu`tBcVeYn}9DrkarG?ip#ZYukk$>+7;Fxsl+JZq(i>lO4T4GLPd zE;Lfmmu`&1TW8KP|FflBF_^lM<_z3=*PrT(zIS+(^(7z`9iEtnswCj!0+P*!?l1z!mlPO5! ziJw5g6WB?KSMkKDunR!nZLvisF9n+QL164p#j2L-RaeqU7thq#ByJ2q)0xCO2tebw zC5zi-g8KF|f42lHw|MjSnhPlFWRkI1P`A9f2vFBQuy#%2PZ6XM$^(&P91f@DiNs342DwE@v z%U8}H7eh0Xd@2^%;Opr%`TK&fb=yuBhq!}FNIb@Ge z3M1RM!+rY?Kt7R$R49Rvky-xtaQ!^U%}u6p{qnE>;h+4V3+z=| zrl)7*6NKlrI=XhdjX~V*urr^F&1zxy_4f2YkH-yOk4M?MAmI0d13^w}A6{T|VoAy@ zkd{2oU{eV3NHhjngnC-pCv!VZ8d&g!^4=Ufp-s%cLnj|o+2qUC23t-O6bsV4)ZW=@ z;2GDd@|hme#+>ZcSk^447X@o+vGHf!fU;Evb*<9bnjBi}s zU;BrzKy*H2GRR|2VLe{c}|fdSa#bc4iO7@SiX0<=Rn z)maE=xyev%Y^<5RWk?H(YL;)C6csJ#AZ=F0H6HZ!^(#sWE2o`ylxah6t_lssM6SSG zUODC`*Yd*E;{;=mhEgF1k@;!3dhs-zKYkQur^aD^dJ;TdAM~P%zI*>6DCAL4MZ$3P z(!2P2CfK}luXNx8kER2`{;0=pN{>e1(Y>A82vZHHR>t0Yzn79$0{DG-3MvzM5!*kX5s z)!qvt2tcil#EJ}02~>p(3ZNCyP!DMq-EFD?TackGD8Xcc5?n?KT2P*+`nH>uY^HIB zGPF38N%{RGEohU|#rde>LkrerZGw5(nyCKZxSx#9oSCmywr&{7;ka(e=W|0dvop?@ zUVOoN>eOj|`}Upi$cG<|^Cstp&wlo^*MI!uAAk122OsS0)B>GP4`}aUeGZiN+Hw5H6j|v26?|5b1;r zbc@x-1_SaF(LM|2Fj;T{VQU(AwgBGHI3N7B0QFiif7i)bwbf~3wsr%`79CL5!EZv} zw}qUhYYW;e;K`&EC;8gn{{t#Nql&I6?u9{Pr!HQCo?svQ+8ZfoikGP>F*7~Ee(?16 z!b3ZEAfWB={lEP?NQI;LHw9GKc^1fOce>aZ<#c-)SZPaUbC<6dc#{=eUO$c}Hs(h* zp)erj$>Y{&zODy(Ep1B$i=kG9mOZ2R0F}ph*`LfHFB=DI87rEg(4ehATB=oKETW2z zhi75z@>w|d&g&4GnM6om2XBv`spx$N?}aqwu7~I0;*<99_x~Jx@bS+e#Dl<9 ziqPY6a{Yb1)@(Xsjm4ty?zyw@#?hk?!->zKL-)f&4?POM_j~_4PNrB;dJnC?{A<7M zee3v%=@XZ)TTY(4@N!Qe*!$r9_re!{_lvBf8BL%*`KeFg@fBD6Wa^s4wed<0yi)tV z(#lT8x}MVd8J#}ifKFBlRHO+2`Kvi7Xgy#}LR>DikFRMl%SH5Q-)c4E3FBcta)LSe z%n9VA)YXN2Jp}on0&`Ah2BIY#i8AwgI-6rr?eRnsp&ny_a5jQDf!sps-T_ur_!gXP z+wo%N>H^W~1JITeb+#@7%36U*m+OA3LA@9NujzoYR^N;+O58>#_u2xsiz1g*6Eh9` zo;nr%{g+;bp7ZBf{<8<6s5f~t)3H+8Q?H==H*SE#`#-{pmQ*Y9^ETbJM+PMVBcIBG z-Qj|vkqvn5+u3BEqMq})EHg!CGij)bRmdb`Y+^7wag`NpqWj5$XG5@C+27!}fnTWw zw*+&G9gn}apWS8!P%IAC5}3C+7xq=qfm+R;Q6bLhR5raYCzt?q?zvhp(O>u+3~$&776He1 zu>_+-gDglciNYronuF(#{0L5-It?Qu8{y!=!|?mR|HrU-^VWtxCXKy6|M;T=N8fsL zVBhGLLw$XH|6$MmLk*w%OTYX3|JCkrUIRG~oC>>4EBiNFoiJbh>;FWPPT_a{`2Wh+ zEA%d~mBC0I2voDTYUZhRqTvk@ob0wv34oQyEPY9%Zt^~<+5pK?g z6;pg|B|kWs)%^%?@^AI_$WiPjHPuVbb&z4+7pO%}YCau+2m(BfDx5C<5Q064+h{z( z6gpju7Q8U&B6i}1Z9$mZtafnXg(|D<6@>LV0rj$8u^Uj<0n}T+$Qlhg6V%$?bp2f# zABO+}RIj2%RVCwov7yPk%06fhs&Z1$5AN835VdKkO{B_AB2ETyOrZ>xVzCT`LIs95 z?SRny4CJ#JRMhxIck#Yk_8wI10dS*gH(AJi=wNEyhU1JHV5=6I z;*_XvGPiRz-J~dK95eBllTn24^^0)v)LRe@&BC-!Mc;kkFeKwKX6inF`Z#Rdx)bg` z^Z^*%v;}(bIpusB0)7t+;dhrxCvn0PgX3?%32(mn7EI5~z}?#KmV<HD(Qdx{`WQYSsyq?qcMx*mKvvB zRTYK3Y408l(yAF;E!E@c!7-pL!h!n^v!K{r+qPBa!;x4x9?RUgIb%@atWe0Z?g8j$yk`d*zNJMal+&AvG3K5V~!62=tQW|xaGi!J(-*7 z^U#(o1+m#lDC2A85lkgA!y}}~Tn`#hfu}4p?`QIKJH>Lx8fwi zhHEZ^U_~%rKJymL-W-GJiR1CHvyWI zIGNa4Dw?k`bSdwgRzoEr498lKAgrjt*T9eQg zsHl>bT19^7N^zlzCUA>n@|8{A4!nMCE+;(w<*&fd)-A9ZVLh~UtIkXeU_*txXWLe| zXXh?Buxl4wJbxkmyTAWO?#-Jv!O+lv$?0?jEmlj=YO&x1+z!E!VP-Y-BHZa^`cR<~ zU@6cw%TiKt#p^y-EWsRtkd!)YD+J@nJe2V>%v~pyT|(6xW+rh`=&H+&;%5l#^j=n` zs2$8YP%e?G%rdyeLo*ES@11@}gFU@YPaudY+yXtleUQOP#Pq}%_yc_m(EAVH&(f_T z2;S50ya{{v-v|5e{~&DKvYiD0OXVE&_x7+L-!!V|6vFh>@ndlO_zAdj501Ja_;KR0*?Gf=_+^SMgZ3!PLYJxN>6} zHf-AqU;35bW}k;9L#k`DuLngIt^;_iHzp_H=sPFj+gGpSgpI=q#t7`*yAK|FYy^Jx z*3V#a>}nigZNopct{Y%?vXof2uIeIdf$KodDmWRJ3Xm_XYdF zjg#%rDo4(rq%cU%z?@Zd{GPp1b!$Uogl3edGF7RLCLNfA2%^ zfk!{VBEH394jfhih6e`_gc78ZJVbFKaP-xen5mn5_xIiR0r>1^zrbMKTGTa$H2p^J zUc3b7-hCHNpE<(<;||_)4?OUJ2jG*x_-`OzszNN8X1VpW?8alJ)nHn&TVVjd%b?#2 z78G@_zI7aqJbNDIBQdGJzn>dH1^)QsABP}Lob(I5cQ&0OrM19py6%Bp3zC3ZTTS(T zSVFT8&Zx+FjyDtqP}9RWrC!4@FqF$v2T<)jM15y4k$E*lPz$$k%*|2Dx8@Ai zg=jJl(L@%c@kvF!i_BljO7 z+aREE%i$686Dg(3I`tjFqjdZ1nf=+xo zslpju_isG;By8NUA$;cId)b%XI5zmre>r0Jc)fclGMN-!pTi7geE#szM)q*?SLsWMGh^abI2NyVft{ z*yt;0Ht#)|ncnDvS{?`i|fM|tDBu*;?P%F1b5u_%T+SSY#C#l6ybc2+n ztL&quNyz2*)Y`gW2&U6d0@T{0bOKJ#$C7Mp>;{yzc%4OTXRbU#RE^~Jp_=me{czv*Ps@R0Y7KA2WKRF}B7nC% z_@E(8=<}cdCHThoj)149x6kAD^m+Y3=n2$%AdqP}lqtZbO`BlD=r+ymHqbxFwhg16 z73*IZ0h+?UpP8A00xIb78`l|hZ%mwIs(N;I1_AA3lX)_uyIf9G?gLDzheIBGPM!fN zkFX`RPVen@dzj_Wh3eeDWdl?99?$)#?5(KM^K8;UZZ~?LQbj&iilw25?+sgaL8X`h zpWDt{e3?`VDrE^`^D}Vb&7*MiXK%9a?4gH#4u0VipM^j9(?3_-i;IW!Ih+`szi<(b zpE$uLNB7*b5B8yw{_sa0gChPWC0t`!>Kjp5knTz;y8{6~Lb?}52ZPMT@yc88!1tax zLS?HZ{EoPR{sH*tM<2)W)L4Imn!D(MJv%oG)6>&z8=jxbrzkoO=Wd`>Q|l`12C$ZN zDNe1_wVayJrqKu2tjUM5m8M$7jAm?t+T{jnrT8?Hy8>B`Q_bx16{pqGg<2Ie&>bgJ z=A^}|y3Z)q6~R}n3d(>-#}bKUV0J#V;=sNW0?IlDwGOn)1N91MjuF)MZruil@45^2?brc-^;dsgxp?8ig0Z=;uOE7P zWmi_EVk~Q&qtwn^k_C4iKD-ZhZr=fC&c4_7B&K5u;A_&lrAaCi4GNS3MJD|D@ElXO zlT(u{ulv-=vusNWo=op#g7IFfH z{MhjmcuW_`VgWmMZiip|>}Oddd%j35mdh{|ji4DsTd?tE*e{ zj~fEqv=@+!CUTGgE=Qz`EYFP8IO}dBirQnWKm$|iCG8lv6)=mhy-r%I3uoz6YvRY7ICW$K_U@jKnfv@ z(wQ{8{PGL%(Z?QVfT~tYFh4)foJ6|kAI9)*1 zn^r88wZ-jp;BPem8;1H}xW5l3rf1>hqi@6IYu6x?%}K%jey%Uj2ai0m6FhE@;jGpF zR^;aog+nkqI}4F$1VXdZsrl*2`Ps>vlM`1jUk5qOOU>1u)2-`AVdIbI;Dy!~FjJtS zR5Ks1kXalWL0hZY%^6g7S*fY3J=S2d(Ws>+ua!$xs}I!*=BqHT-|7`PXf5^Rh9p%{ z?rtODNn9xxZhQ0hs{5(iKAF3-Kz&OC%AmdZer?tgw@^Tr1J&1E+qT1DgvGv{I}t*8 zxIA_P0euOcdHHSLVwU4ZOePDPjE{|tEl3k0rCP7l^a_srzhrtR)AGg3S6I2^QG}qO z^eE`}VMW32usa#htJO_-{MR6hpq;!q!Qyn}dg|>JF+Gn=>tsUTh2PT-RKNQuoel3d zQIGOhOHzWZu`C z+X>9fDuI)PNF-be&rV0CC&nhH$FJYWq>=>V5U!-AXO#Uoc{gy)(98Y$AO4B7-U_Gi z?ONKaeXVP(UOuQnQ`Pm*u9?O)fGTG(MT%Cq3NsOLO~$bWow*kDzgC7hPH9i4139nD z8<#JR+6bq$)0ArNfLuPcEDOf^1~ururDls9WA#0|902K7$%1z6KOF4zyI= z=wZB;{_T+$U;W&+EgRr(|LosGI39;Tj}7v6xmXncWsR+k4q@;D5PZh-ljDL8iK zT^JY|f#3Px{v9~n?xqTxEM%#47J7T!aQ5AE@bc@gGa*0?hA4_Zie1jP4=WQtITr56N@$$%bY>rgNNC(mC* z75ye$!3h<9hf*NW$NBw!)^<~0?}CB}OV!%%K@*K|I4oNi!XYU-KN}Cv&dg6-dv826 zGey%&%+rnX&ypff|iYhy%tqjr`w(9rD6rdBa)1tNWC zF3`8hgoZ6OwYuR@nu7c&bpy&4WBy(= zsB35AS`gW&imEvH-mF$C@p!h_|JLb?@WRm(@Zh0+uzm9e*uQtD6!70I`iM9n1_v1a*M6V1NGQSK#A6 z|MPHld>m#XaU7dcFdj~@Tx)88M4h&VHg928z+HFkQ#vD>Se`Nkana|sdVFyH+Kq)h z4HWA_Dv^SCECvHO5%}g)PccB#XFGiGARIVw06zH0M_8%m971}0Cd>lI^shzDnYL~m zfi0uMOhr%6hvD_NkHgc?J`b@(QbHBYkxzfuuHC54dpI>Ew_Zh)n9)Wp=p)%UKIOGN_oyaH%a(o~i-sX&^dz{{kf7jtgi@xbo5Yq6w@z?N1b zV!WomBTOKRaHlP$QpFHl zR3VXIPv@J<*Tb5AP_6L1ScPh#0@X|b$CV^2vP7q(ptPl=t;1mjkI4x`1APq6g3`!C z!Xq8KaSh>Af#+X(QGTy@99&KpE5_98?**sR!BjL&21ZAQSzF=$U=Tv6meV*HNaV}- zT5^pE(zB&XnXN0a2snYLmL=G@bsO-K2zT#4fZqeE=v)CJ*#b<(CJd%-JASvDM@C@# zrVR|#beN1ZFCCx_QesW6D+CF_R@Akw~!MwOA}#rC_nSshhJ1 z=~}P<>t|@qx=Y!cg zN}~p#vDsU(2sXRW9!pZImYTZ0)(yIHflh5}4L_7ZF7jl6>lkiaC*;0u&EL|pUQcSr z#%@5_BF*1Y`|p2^^wEwtxDS9%N*CgJ_ux~Jna|VUHGAn0*wx(|D+F6nhhh}xMNm^>G zvAKHlcng#G0+roNZ5zSeXt-ZgwD>ylQ#U&{j;l3)k!}Z2ul0bk4t^GHjR6}6^@-rn zh?vXe_@zXz6WCR_8&saczpFDbgk%D$XD&f1l>)EJ!C>R}c~}|Hp@AS9TOK&D50$O} zBZC9Xig$B*mYH*>5#$sQHy4g5+c4zwMO4WV2KFM0phl%$DdO!64NkH@o0=o>AosvV zu>0I@ummiyclR!^I9xCniZl(5TgjzVEVGrCfZqqZw{3xK2xwYo@Uy*h<{Ui#>KhP> z#+d1ya>V`q0Bw1_6;mG9OXnu;vSyq-k-={h9%J$xP6^ zV1-ah?#Db4y37DdYMs6(n??2+yBs!FuxVtlpA}2`@CWY2*O7~rkUxDs9Azf>NHoq` z_>!+X$<$_wsc9-fTBI822s|lQf@rn`c1MoYm00b?VYk7K&6`;6_Rg)F*+k^@g-h`C zb1%a9%_+*E7V-P#$&9~Ybb}#SjK*BOFPa#TrLwEp9HpU2vCw=XJU2Z*dHw1QoOIoQ zg(+8bJE;IVhp(rZ5Psv2{&ZD8_qtcuqEcW)9ZWh7I)kM{TPvbc=S!}czATPb*m-Fgi|=tj>E}|*O_&XO!#C5w_7bN&53;Kw7+S? z2n*Qq;xSOWU-GZV8Mrg7`(GxTXWKlgeCqm_FBDl&U9nVJ_7_Pe`t6%G!j3JQU>7Rs zzTO_VIz9n!pF9gseD6mraF4z>nh5#*y_9~%3%Vd)J)o)11VyJ)R5!J}OeGVA@XS>K~<~x)gHW}J_3Bvk>j8l};A%$lj{Mf^8q^K#5Z!>XWd@YBe1G+N zSeu1YD8KgQY&vxkSCV;+R=c|2H#j_CwcDNkK(J2`Ocrl%Z!gF3W=~H~59N1wdi-8i zciZk@ASb}nmOw>0!B(49-s%FUI)@_9EC94>>Jy}^0XztI+@+cR89V7@~@LwesXq>`P(O^XBHd}H7L4! z*LK*23YtJYJ~<8NFJFPLJo#;w6OG@CXt7v$vNH7HghqedT3b@}bKpdTAWf}JbJ=V) z8k&pGPfyKFT)lKH77meeCf{>?u$Zc%7X*u~wvz4wyU9YWC?EOI!)!)KN%_=`hGt{M zav8T}7EG~Z6S)3gQ`5LH0FBK6DNj%m*$F4Ge%bl~s+NM)KFzBJVlD8jX#kqO0^8~+ zm)jzsZ21o_Kv1Uu?go@C64doK*I++v9O(03{px7X)0Su9pm?`~A;7F6sGQk-V$28d8)`v^{gxfxY6 zsZRo&K4oc9CTX3CnM#4jGL(LnhuK(?)fg|IewXE3lckV+#6eWVl(#(8AB3t{fsMmM zY#e*|-h=E|Mu%$tb^4phnK{_Ia~qtwa2bxg_!^8&JVCE3;UtD57XnpX!_UJTQm%Bq zXf@-8#&X&w(!c3 zu{4hsUt|DUU7DBFk}*;P4S73&dbL%x)KFdDa!}s^0cGu2P0RT79NXTUjWD%Kzav3a zE{80_KwmHTy}k#Ca%EJ} zv6&lVQ#Z%1j-?ZE70S(m#fobY&|P4+SSZ+n*7}7&P7sgd8X=%35-=YMGk}-bEV9bd z7pPb~%_0|7Uz}?kfJW-tOm)A-Dw`EcO{!?s`>~AyXnGTRn*cODtmVkFhGy~ARMZ`z z`W6S2E#}dNU)dcTQ1-s~sio%6s*m%#9Tx@cC1uUE0DHY!H$a`%WISrc)m9JkdgU&w zb(6iff3Oe70ZUJBUx4Fzlc&eugSQ1#@09n%R65(@ctpjQSl%{^7#HNiEWEHZ2qW)xusNL$gsjbKx7%0+MYWP5 zuQoK23o+@ptrqe%Q=u59n(MlfvG^fT9#`|(OksXzDl&U>V)EwoE8~S+mQ-^{QP8u> zenMyFF5M0)x(n=8TFC!Rm4CKx*=)$ACU8d(+~IJ9sd2g;##K~WU8+E&o*NP;EHI6$ zDqC#|qPE^Nn8LY*gJnIe8>V@+GKm{&n@Za~>`1Ijv^!f2VAjYZ~WLgQC1U5n1oO+hVKYzB<=xm6V{ z-bue{7uc(?5X33{VbjPEYn@5OQ0U4@rYL_g&3xkt+z%_tyjnqwvnCy=q{+l&GArW_ z&&)jyTW=b&s~Sg(H>)$Q4?r_UiR%K;7L@Ah&@A3DT-TKh(BgT*%B#|&VNu{|(gV;dtsY+0*q?NY0y zmU^pw&wbywc<0=R$coI$y|SvZs^Ye;A~G{FDl(S)yZ>{}JvZRjVKu^r*}Z1)+{nb# zD5`C}duU`3+cvG;>1JkltKDG_hXR_L)6;sL!Kh8dqA}0%V$icf(@@@=Efn%ne(vfa zR|!!?7xub}R)D<=MS^TeHeDu-U!*#TAeT2f@k9c`l+d05J;pNggc0gA-DM`FX1*M} zd6{oVs}mCFZ)!aGjm%om!kBC+0IecQTn2D?+uqGaaDN1pwesg$7}B+`1IlEty6C5F zB)EGm)0tF$;Zwi0=i4(8f=$ivU-dvHYsAULa9_!0ZFRWYO#O~>m-bnl>!4fOEe!vkPE8Z z9|$7MJ@Z4i|?FM?SbaKoEJ7mSX_-uC7{n=Joh63}M__$*fsG;8 zi|{sEEMPL3!DuvtUaxD3qkP@={~!dEHS0u3gM#@65K;olS^{+o^cGH0Sz+%UWq2Nx z(s_HS6b0D3uCkdQo2z4HuK(m$=J1xQ>>BHhroW^acnIWWMJ_%ZX#_bNb`};A!GxoJ zSX{UTIRrU@d)Q^GeOy~n)q5rJNn3x8ulbs5<+msSWnBQZ)Zq7Cqpv_nDc$So|A>dA*EVp`@Y?ftpQHP4 zsJH%cOYrRY*fwp-wcbxq0?L}7eSU8|@AA^gG?#m@jLHE0KCMo7+UatG)71}7H!j`% z4Zn=wK3d4`QB%3Av($2Al5 zNGpDdejcW#rq1K)yF^G|GMY?7HoF7t4kTRzd!${{3?{bCjW@Qn zu{O4C+cq{E+qP}nwyll*&hvf0V6HiLpFZ8y)m2zQm3`)$@kVuzYd@2lU}>DBxzqHe z__E}>O8(6JqR0y5rFoH|0Dsj+tjA&RC}WLEVNFnL(({4z^KyP}B2=5xy>UxuZC1-? z{rRy?1)xq z-MH=?dEQ@|F|7(dFb1|SWvVg>E9IqKnKr4UuQId&^4ZKyx!gEidV^_pXC9O?0Y<`% zeLihr=ihrL-a8iM@BB1GizkNkvp=%sg_uB;)isryKv`Db=B34PZ8Ac!PJ*~cuqQ0= z$h~_|ndg&TbK7M_r_tg^kLP~e*J+%stE0pAefBdxzW%c|lwnkvREF}GRS`quU zE3~>t6}EWSOBb`7fLc3X41Y>U$*!YTCi&M-3w7N=jy!IwUv8KhZ!XV z9dLc;-godQ1cJeRr3cp9JhkVJy87t(D~O`WnELo0i%Py)ovJg;j35W>b`(&s7J9M( z=J$LwqQ6BH=(gi>ZuOF+2h}%{&5hi9-NYJj1;}yn(ws0oiLheVu0+VbdIn-^E^Vmt z&9eX95%vtXe>1X=Yn1}Ae)pMuxdO50PxfTV_KIS^(#zFqugh?I*~FZTSW*?ea*Y~5 z&r?y!^BnHq5Sq8v*Kvc^2M0Ovd0f%A-?yIw!TTg_0BJoZCnvo(&%dCPsuB9G?N|Fu zoBD0cF~uN)ObJF3b}_`D1UO!1;kIAU2p00-T)C1qB7W&ak!g z=pn|l4;>zL;KI+D#>Fjb)|G_mnZ_FnH&3r0&~`QQD2JxYT&KG5U|C)Wu76H%kPudP zr|(;W=5oWtdp_*sr3_2R;{gPyTscPuHd_b7HJQjNFMS}c4EziKJnAq7o(gU_Jv|*PWdT&ID^$qico~7y_YheoHpYL<+&BUL z^Ma!%!oIl=8SLS7ZvcK$QIaA7W1xx7Y6QMO#MR+zJomIBjCwb?iUACC7pjtNT_H!PxYRE7q^=Y4h&KBV5`#r_)s}D=p{Z4$DFI$?S>*HL6CX1a+ zNyC)TwY3snMvD$$3R^k8`L9t@{c1O(3)@ zBB>#ifxS64CKeWn`wwC#1$z^uIn`<;y}K7{UU1f|+umMc-k05ui8B2$HCb}Z3IXOQ zL2IUh`RfKD{w!ltN5$jCyIlPyRJ}{i@c3@FQ`f)^3vodt#)P(!i?Fm1jF+#d8mBaE z#A{ABJV0uO|IXr_3>hj$_XRk@f4BKBL&naJGAXOBW8?1>O~V+mDbK2`Keh}IiA?~* z*b43;;GfqjwS`(d@~VH%A8diPJ`wlZJGq`Rbwb+iWcw6~8*#b}^F8%5Bl<`Q^9xHW zS5s9@Gkk;sLlz>(j~eMaL1H9tyS}SC@w6~0s;18LE(M&(a8K#W`kp6@#0lrf`a3fx zg(8a96|e69T3V8 zFfj)ep>$yH6nPwdfmA`Y>mw2H|lf0=M z-9j%PPPH<$8SGMriiSziyQYMuD!)bl{A5G=bInGj>)zM0`!#3x6%oTE#s|R~0q-nH$kD_y;$oYee9m85nZG)A7 zb}T@c1`&pei|r1MDKRoLug}|&ps|$cmd8kO(^0*h8}`ReyB-@*8VF@&f>a3IzIh{y zt6$vWCm5FW$nxJwpUm_o%y=X1k2xRR8JG=45Oi$Ai)VZT#)}cLirO-Pa zeiz>(_r=a|UPlFP9?m#KoLum$=ija+awvd4UGpv-?dkBOmp7tuaztU?Xfr>1?6@5yS+3f-wr*nhWen#ebu4l4vNU+Bhg6OpRaj)WWK+UZ|Jh_|Qc@^$->2o#~ z9rEt>PaZs znt@R~EMBK%XsR)bT1G1~!_arxj~X2l-Mjyf*;RKkcc@2*0D{le^#yfiIM9kK3%Q8? zZQzVI$xcJp^1Z@mmDkwG1I$%YVU-VXbp=efN-lP0<@2tHU0ZaXbh(vF)+R5ZniXYl z+AvfXfW@kQ%~OFVxx|;9&#j%ZyS1-Zb=s#NrMIECRZ4B%eKPj35&jfxTXHavLA1i} zhjWs}yRfgMY?qUhvbAP{Q^s2BSmYQayMKSD7ta@wh!sE+;noj%54F}rkZ`n4XM*Tj zfyIps&>&QhGA{jOt&9R|q57xcn}vyKIn$zGI9ua)#f71UVnN@SRgQb(K$^Lcp^u<(v$El5~E3sn+gjf;3S1)x?2cB!kpwk4y|ZqnfX zGslhetE05M1L0gwg>C2dZCq$cN?ZhZGU$t4>b}<}zSS?M1#yJBk1soROgKUnXv!yo zJsHZl3Q50u&!JTdv0-}GWMb1SouCaAx2-KD)7r_Vistva7a@WZFeBecPpR)95q|gk zK%WUcUZx`tT1c8|a;v-sB6lP*ps%Gx<-(GWe)s#mXdK`8o-M8HMU?aam8idU-7oJ- zCj6WX^FhR8@dE~og7oKizrwV49ou@(&%>O#M|v4jvyCdHel@dZjE;h=qTCJ%$C|KAi2krYis2x}m#vME1#T7tCan0wB~}6) zv(XBcdJ00syhjgXtWtdZS{2BeaneRH-G2JC*mSPtnvw8@@14a zatLk>&shiMw3o%D+lj9Mn?M~ekSAO@VZ&LD{G^M zV(x`wgPkdUrVR>#y4EmuBq_vMBuvOVZW|PhWx-n1^h&3ibBBSG2Ni93gqi2WT=iHkUw) z6E|?Thi;*+C9*X6mGJ=W!||!Q$(A$3J}B6uB>~y5)QhS#W9%4LXCMW^epn&TMoJ#U zK&~#`yKK1#A39Jf;i0@#C?~B#;ShK+ulPw_1apdRLNFr~a+Z!>E!FhO-ChNG>Awio z93SeqMiY=Vc4%NTrFBQxmzv)_Hgky;W;r(Ve(Mh>1Iee}2pD7qYwA+-Tl$^mtrQ6e@YNy3c(6@jjM>7BxJ2*ZcR2Wx+-AL6(K3l@%?mv^EL?WuJ8Q zs6RSocgoJQIQR-9!TopoP)0G$RtQd~=2+-54&}4NfA3+tXhS^^_acy2vz{|bo1Q;& z7OCK{^{h?J3XaWdkyMOktcQ?|x`&kV(>%A+~*KBH@fqXd%j+vScr<`>d_a zrDVIWId{XVx_}c8$q@rQ&T4>0UGb2$GEQ+Pcn!I6!Y!e*goz4mJ^ZB4o zljNO|*%wFNHcgY<}YBcg4!v( zf!$>?gIbMfc!^?JxM+b1zv&r>2PO`!^R!2u0VaA?Tb%n0ADS zk@ZNQz4N^XvRX7`fojQ8Ez^49W;khj*;L-CS&%Lic)u|YIe+~e2#y&ATu3h&8J8`6n?YgnV} zKNo9gmwRu~8ffM8RghzBnKZmC3w3!N-&6;)Isk8+^afF{e|_k{j+d1>R(UH39^%l% zUz0>YNE4_HPVd;bDymFXzh{|c$NB+6`IVjb66|Y-#Z$P12i4+nL!r>44LhDXpmmBU zFl2~a{-A+B7B*?vQiqoEIx* zIcMa$!W?z5#KvJf5$=9$D$1~%+-Y?<@M;ys4z@Oft@WCs?XD`5;#qA@!s%@Ot#CtF z+5u@rqCjy=m(lZoAO)TEtW|`PJXfg2+lXbz)G&PtnM71mWvO)j_;K3p-^7-OwPPpB zz4qnCmQ7QCCt{^DJiG<_;_y2tcdVx1R``5{Ce+x&i z9YtDa%kob$kD*S+;}1mHGoy5jb~(W223h0|rs*j72<|o+@`bJ)DLQ?&S4B zxQ?merdgM{Dm8$wpX#`za`R6W3zi1=DD6vk$Qr)n7h~y=6oDnuhXia!ii(0miXX1pR>%R39MlNg zIjd9BNj2!Xx-!(2RB9>>lgq zHA+@&mm-i>8URC_hy@`7~*K8WhiE2u< zIeB{*o(()2{+ypo#1wz!S2}HH8CXz+%eA+=m3x*+ch6qxp*eA%@xuo-_}Ka3OJbw* z=i|_Q@=D*DII8#FVN&s3`}XDnm=7D;T4)d(2DdlD6T>uT>2mP85p(m}`ipypq+yf> z_p}WMTcz-)MSnfXcz*Z{@n-V!>ZqNt7bVr+qg{1f=R1h$JhcH+ATzJ(4wkJZR#GscVCiD5aulQi?X)$zGEenHYs-j6B z;K&y058;)z3mgvQAA;&Ug3$5&v;k>#7Bnbg+4lG=*dtEJFqo4>ywey5Pg7J!~da6qhff!TQ6uBlG$Z@o;b)@R*a z`Mboum%W}t+Eme?)_%bMESQ z8xY*pazNw~hnvK(tYP~OUZ!R~D0&BC%iX17q%8PnV&Q8IEK*9;R%0I_`LN-+TWy>V ze9m~60vYulJ8uy{bddW0#kj8_jIFeCN%kdRgcV6MVD#qF2$Stf4)>GXyp7MF@9+21 zmRl;${>=YNp+bO4BKs84AIAScq6^lriMu#D-(C|i<)HnWe-Nnkn*Cd4+--gF+Csj? znK#S79p)1;Kc{_d=d3SJ$6*$G;#`?IU;=zB_?ew}`b&k{=Inkd4aOs4U7nH*2l&IV*J1>XFfG15iyOq}MSMs| zNYl~Lk=B~E?bXYjQG{?_ZD4ITy|Cs#@sjQDuV*ZB>;=WZM*P{wWsHz5ufgpv#RtqZS(;Z+9|cX6%%9@-}F^vUJ1VddKWAG(%`# z5u)h*GI7edwyK7AsGg2#(4ooZ{y}>uQRnDRIN=qWgAtkJ`|fzF3l(6PF}3LpaeEJa zs!f5==KOa!v1I@5MUxCwLqwDhPMGYuytHt|;D>qi$J}ejO?Ss-Ts0ytuyuu+CW{qV z%~!2>T4!<-P_gK`YW`E+2>#jhBX^r#Wvb;>2N!X2soHwgi zz+527ZL3zX^eI+QH&dvDGsVq#f{JomLl@AeAKa*bw5E)oXJDq$VLm<_lc!h2Joxi& z#AZ_=!}C^K_$8@RtSuL=4uw^G=&0@oO_VD2TZ7v{R`Q~&^Ypf*6UZ6pd z1!C;XnI_|T=egA%iy4qMxiSA@PHi6jS}>EvqxR=lQlfLoA6tmU$o_3`nvMnKBD##B zjIz__VdItMRWE|Yt@Rq++@3d_{|*Vp3|xS-sD{>vqVIg?7J0X0C=;RauZ@>x(5YcrEZ9F%gCsa{`^>8S zR>&d}HDss}Y2N%j6hJZok*vnZ_}6Iyx)lp>$Sdc_ zhq$J$E9N23i?}u}4{ApmmS8Vg@^|EOCpt7^viruc47G<$-bpZ8imO#GdE^Oh{3#*o zWRt{M;o|quT6#1|n*E;A$8ov=F=Q0R=M;X<$G3X}M*@N{M(ZIqElE#bP_8j;V$adz z+ro`66iHjt@VZ$nZeD_01-|FUmNn!1xq!qz)z8H#?vAeF+V}VOGjZeFRpx(UsmX+K zVY+{0maD3&>ZL9KE}EeOBz&rY?HZMsby63m1#_H{$Sr4&Z@z$5j0&89>)xVdW$rQu zjBE7fAObfL@f4Sy7~GsXSHUNTr>xvDHf@%12@TKpx0oKgp=^yh3ml6bWC@``_Q? zB)<(&(MWXgv9YPla4Sj*H$WXqW^Dl%L3y>$RJ9Xmfe(h|iq%7=E&@QFtd-;`*ZuQ0 zJeAdBs#naKqn=p>O8^1%2V|Q%^=I$) z&)I9?@_E(XPCBD{By~ZJs-8Zf$5f@E-(5}q^~WQdQ4vZK)a+J;S_Iol7kn90V)KcA z-OQ|Pup&e_>#|aUI^mrG8T<)j*rNWxW#LO*)yvZ9k6y@f_l#3$eqFQPyT;cE=}i@k*{)Emj+opsl}_@TB$UNlVd4pgmN9Tn`iESwd^GWP1;^;JaBD%B9|E zNWB|PXSh`!2wz~iCVP1-smvX4tva=@axDI8MfX%KUF7PNd1WBrf*>z#l?g;u@N2ig znl61Wo9f4)Hu{}55%kO}F2oArv3yRI7n+nkN#o6pT~|n;AJt=^pDYEqgSdq^$%V5f zuMY*>qV!vQD^lx%7eo=JZj0zm1!FAv;urex>^fl@R_L*iXU}E0vyhYS@w0n86u4Hh zl)<01p2V@N)G4(BEIKx9u@*7(ZxoWY8h`|W)D1VRMGzjKOU@d>pDgKa*~bb%qDwOo zW@Q^v_PZQ$4*_FJWS{=3v1^K5f>hC=shbbmtOCr1V_T*|aGNBcEh)KAUmi9#gP4f> zbrI$=%4vZ$u_}1SOICS`5T}sZzmm5Mav7FJ`j0JC|CVRZ#;zLh^ZL`>8@V|_*3eNu zVB-Gqxhk=RJfA*o5Es%p04lC7W?fZ0hq@2|*lHHePLY@zc)D}SwZ{Se@V*x?Qj8ihWpr}0&-+PYKus8H?qr>Z82}C zR4lxzzPBM<;QT}*#}A%4O7U)hP@<1N&`*fUq@lx+2FL}|(N`;?v02`|GA^H&Q+d<{ zn*5%%t4xM%0c$RV*q#w!)p*2KBB(_TTboqBL6h-X*TNXIG7W^=1R{R7ZxH~e)$Kh* zXFu^TeG6Z7r>a|tf~S1x=Y5Jmyco)?nPXO@^XEa$dE`{uwaPuAz{i{C3G_WO*fq$W za7(RR2ihT5tFNMJ8m*{I9g3)6wW#?(FI9()f%Nqo;Pp2(UwsBOad|3QFlTopyw?JC z`849X1vQCGRgt>N$6ON&u%=zL@%7DCi^vz(Ey`>7_oyjdrrNV@aa0516^BlJ3t#G* zTHc6};5K1Y?^aItUkD#ix>Q7ZJ_~KRuXKT1>TcJmq{9}0q!RkR>>;`H5-x0RW`5s| z5b0&6r|0MCb7>SAm}@w6sZ*g_ZimmAH+yv3qgmR(iA? z?E(^)1O{zDT!yYjpM@{e6kS_Rh4cPUV@m(xRx-3Hku$?*Os54`&3HdOEQDRO#OS=A z|AbaS^u2lZjq{gNav0vfYh^%W~}1xvl|7wXC-CEOe|wFRaYce0#UKl zUkp8U{O^yfF-h7aqxpf^FYYb_^VM@+et!NT3d%O;Z}42PfCnhQTPW6;vM_XRPy!xs zX=rDG@_XQZh)#yz)0)|eLY^5OXqQ$@4A^BC;1Wn@;`>|fEPZ>uG)PGh)b7b`-F>CK z-vxU9G&2{Q-h;BUb^@GJ(jR0b%Wl4!5>g{gE|iCd&`wgd%*)DXF2K+23eAekw)x%LvHCan`uTlD zBo_+#`o2t=rna?})8}YBDeWZ3Z+ipe*ap-`547)GVK` zcC_^iY~TLq=>j_+?$}PqRps>Ym(G*7-`_`HUZ|+3cqV2P4^a9=n%0jCUSmff4^#}V z#_X$Jxj-3*WK47ZGggQ*IWqFL7v=YjflYuDL`rYKR3Xd-Cq~mn}8` zD4(OK^&lXhra&Lw_{QP#z*rXNAn#w4ju!wNYgTwt_92#zp|Z^soVMSvF8%%zfbiDvIX)LAHFaZjnCO)9*@7=ljI39c}mt z*^Oms)&t%Do0L@mA3mh9PhSEnRs=snWPY(c7YG+s=F^xJ+XvRp>5Dv8hUk3_<%INn ze(Vh7MZnZEfpdO|&pM$?Y0GR@QgvUU|cZRR`6Uf_-56X{A zC?e0(JU=`foPZHRujqA7uxGD2)WM#BN2@lO;$!t?(wT2bnxt1if2>f-AYvSd$lZVu z&-hV27r?Cgm?{2t?!txDhl=ykn7f4ujpHh8SuzXD;j`px1}T7W;{-r+pL6Ed^32Z?iKr<_nBZwgzLEfMR5Chq6@*D{f`&iuum><-I_fb`{6Bq59I`h7s+jZ zV+Ht>=9fRcdDmXw?Lx%W!Abg42s9S9uGP|2{`C+G+2VZjHgQRMRS69X?Y(kL@}ZeW zuF;VMVjsA?jRHW%@48bOA@4(Vj^_=K*i2HF1X(ho9`iW=ae0}xF`X5JrRRlb}2P=g5CyOcIQQ8s$BKcFJC>lufL=L{{%0M zHj4MpG2GSdx^{y8xk=u-R4 zr7oT~vkcVmiDkp4C&$K7 z4m2e|6!+NBry6W7GIH5Hfh2fUX0B~)9MtCv=5WnUY%Z6Wwms@XmosbeY1; z10+(Le53y(-;w@P9BFts|M5lxAXRwT68QY-e%SK~uFc7X7-3C_NRa-rn3D7TZzgW% zen52VEz|Ry;3jOy-tz&=?>)ZfGvoU%B+mPaXC}bM9<0*M_ts+P`K#Nu8MuWr{4rYZ z8i|$u9n&0{$Xd4J+mf(^5nQZW62?j>P8J^sTssTmna=i47(x7ny3`J{_j?UiX%%I5 z&#fVimd2&tcUkSiRQY*PD3@{Ju8t|V2IVF2%iTA*ca>yg5d?DyL6~Za3z%MYHOp@(`*aYnWM)N@WGbIWzhtshZ2Ps zqqr)~eY)N+)3y2@Ko;5)(YIt=Sy`BbKrpq+Tb$gHN_SsY3AWN7@<8)foFZSH_}3Fc zxg6-+n92V<_rw}6g`d5VHeoJF>0CXos_6qOm(Yydhhc^u$zRsCh12uBfcy~9b6@0g zz2rpytD&5y$!PAx$bJ45zRm`eo5?n-)ruEwr|3@?MN4 zPw4L;ticL}eA~-|V(1%h$0GFl&_Ef~;B?s!+kH)`5iHVd@dw}7NC(!d`t~Px*woN5 z05w<)57IP$UYZMzn%@Htn_w>=LfAOxVNlx+Sgo0e!7cj2Hx28n%t?|Bf*jy3@VA`E zyH7!|mpVTI4DJ5TK@xmj5@cX*dY#ZMS$q>8Bz8akT&P3Vmbk$eBN7B+>3PB|FRvJ& z12x%f{1``-xu~tup$qfIZNZM@l#yH^uX|3Bxt&e+is z2ed2LK3hX8K#+5C9aheIw#Endm~ zW3q}NkRT=AI5dDE1o^#(=ri-}cJ&sbz5f$&1OlT3tYr(a1d+h>&!oz0*{dwl&G4;M z#SlCpuWro>5>O6J=&ujaH-A@h#UpS7r*L`9Wev#S5$uVIU`*14=o5rD#Z1;Wu)siF zjrhY2)@YY81nnwLq=7-4F{2ELy5PZls_*EC|MBIrzO75gUUUGY`N)Ro^Kl61#qt8x zI_=N0Zi3N9;s#>QEc)!WK%Q1?w~Z;1;oP=iKCEi`-qvxWf6LTnZ}HN&Mrs!BLfdjE zCN-}LKkv(EZXj5$p)L2{tV;Wy^Xb2-Dkj*9=Y?OpMf~YTj~ohxo`oyTOz|NMH+-i6 z4WSWg%UsZbw1wdm_$+u%+)3c~Tj7!GO*1Nh7Ge{gRrR%lA+c@1-X%CeD(GJl!e65G zp^TTSyGbbfTpc7U-xSeNa;Xxg7sK_-nyI?2Q;+_Ui>h9>B}=aKhv5*?bjuiYEj5Tmkn=z?wB!##B3X5-I{UUat>1DvIR_P|I@RJUtL^`7Y3s z4L4aZyH~olx}@9+SYfx#PPrUESg?QmjD*elSU1o{e0pxs)rH}KEcADyQ?Zb4uh}T~ z6%)C|Mo=TBT$q7B2ML&u)tKv^r($=@ddYMA{I#IBh9g)=vFu3-*7UJ!oBk7gs(nxfJU z1I!)M(JKrAwyVP6lXCpa)%{s|@?06}4tZMCRC0-$m(KP_eUN1^U-gl3?*T`BOljo7 zW}xCfJKAqS)FBC8d<%0XYt`sdZk?bY{krEm5L&kW@24m%8g6};fUah+gkJ;pBQMar z=5_u<`o`B{3}BhwBV_5kghbU)>RRLIuG~Kczju3fUVjsrCD@Ch36r@qCs|8YNn1;n zR3Z1?H+FW8ih+!g@U7kiF1Td{xotafJQU1InFW*hq8%LEzJ5QgM58#t-S0lW8#ueG zgD`{fq{+;?%ta7T>FNSQM3x{#ka08+GcW)|yYqscU7L7gFIA@fOzbuk=t)81kL9MX z?=O@|I$fsJJ8bU8q+-!v!=-z!Z<~;Q7BEPeg-bckXAq;hY+y|&xl@;;3EHGs9yzW5 z0-?Q0*ZonUP4NT5>Tv?Zhgy$nQ`TM==?yfA)!jY|YXi=+zRZTn0FOeg?aUlL`!i)^ z+)p}czwg>HaLp6~A*yt_!Al@yx4?u7J(x8{YxrsW=CS?;{T~FU&$|Y;04oed0Zjf& zOG(X+Kh7zaTd;o3dm0dT#caGkZ$GRS;hf^kdPW)ZxHhsf=Wcc$?qW6$St~yiNj8&HJr5b@dJfh3$Pb zu1oBL;$BJKOe@%(3h-kD`$C-7Vx@6Ld?>wkp#X>5ehdL)i^oZaQ5#vL8*%{d$fBAix`8^%h_m8a$m^z3P@9w9UZ!u~*R6r$X{prma^ZEfSC z#Ab_#5O9XvKUv(9Z*;0=8Rge`r8HThFa^i65d z_aM~yepY1o4&1I%MaRbdpc*kSbVN--!vM8HhihzajrKFbxO9bTU{s_eL5Hp{q?ofW zta37C&e$8hE3n^xu&_sHHSjcr+%Jv3@p}%syhpAE$NJe~jviFVaj{qXiJZyr1P0H| zcj{8Qtb}7aPBHgzIKePbJ_D}z`K9!y_$jTM%GKOl9d`x}hdK({Y&_HS)NO!EdF5~% ze*ezPUcmXw@S#IhO)!M!eSE26#^<`{ON|$sF)~gKNFHX#fYl_B><5u8;}#(6{@CyE`}jeW>URWliLGl^An*GeKNC1*`hu`As=&#tALdRW zj%cV3_lDz7CTJJPvdF<>w=U*Mbg}m%Z0#-HNSvcN-X|_u!x}sYlsO7`Zt#8U|6uQ5 zzjmRvfmABm-SRiD`bTA6&^X_xnsH!FD2ND;MACT$y$QVdC4oLBi_qc>-Uwy`vuw;yv&%nvC~IF# zUS_eLgOTLI)#6?P%nD;8o@G)d?dcbbwB2z+U1PVv(FirHS5%Ro`-3>)e{Ex4VwQ6E zLlJ||gR8Q4!%yGad(qKW?OOEM03RDsMwKXPCJlb8vCd79ZZ=RIh@JF_avxQ{IPVd5 z04k*)S$Xq@aY=%Y?Z{1ivn+fidBy&}ep2At*_P9hbBF&g5*A1I$fiDY{O6|(wY?ZM z-HNs@D3!+)La$2t%pwT`QhaaX)=qqkMs`WJjc|h?-mWljhyPmlM+mim({K6$ zs%I(#Hw;4DGI>;=WbH7z<-2S$ZmYNz=(y(LFo(MAhj)~bx_*7~On0r50=Xb6V&iBeSe^=4w-a;CN<&836OMY%h_ zehut4J{olB0Q@*7YrK5=vF*mARjF7z%|;*OH0UiNY?}vAxKyRyS^Y|CT44 z?&Xs}K4d%hoeOE5J4l+M20E$YlKvTk-W^_n|G<@Pd!|*xq@ljhXfeIDoJ*W;nvc9m z0g}>IbP(vn5S($bR&6jjea^`?y=JzMM<7GMR6f+H|0Vt#64$uteD_xo?Baj}YbPBt z>z3yvkfk6_j|Qh(`4pOcLg*h$C`t1~G`XKOV-ljx3Ua0ggYdq#d1>ESgUWdeAu5B> zJD*HZpH&SIwM-C5S}p=%ZUSl|;|04gbtLO*`Ob4JTj1$P-xfKKn-)ZnBj&lrhSDpD zH5zlrFizB+I=8nifAF)@neZn0%d@tR(N?i#tlpP4K%dFJ&P|P{49du5n*0oVpAkW8 zl?iwsMF(e7#wPL5P-W@dH$#Hz|9vVH{9ahf70BXJI673e*J?d`qnF*`;bi)>-gMF3#KuhYA{dUy5ZNOafx1&I%w-0% zOlu)UJG|&12aj1$j(XBq4cUypTxlkTm2GSJVXJ#tq(J`7hUXuLrQJ@AT`6f|N21xe zmuj7+(Rsht{vW;l6(}bC+lM9at{~qy+Z=dyVz`gAu^N z&V^Y0#<3mTyUwj8;5))qiv$l%07yVt>_l;P?@aFNWA+~FQb#cCJ`YGCOtaKu zo$>tvH9|?DKYP^wq>7Ly5sW03zlloti!`a6C#HmK(VQ6>P8H0B6!X54j*gCYdXl!= z=k;Gc^hu>TSX`tuGtR6Cn#b8Jy1rddf3rGmuYr+_K7!Hjzw-%-HBp|Xiw&e9wA6-e zIGtp2Et38tgt$@6B{wLw)JfT_lF(Nvu&>IboJLHWJS z@#!;r2sUl8f|_SeMod5g0gm2TQblfJDmQmPoMD`to*+`HauaAF#ChY|aU(A3k|S2o z#(*1#Kyl|0OHxR^WLm9W8a}%mX(Qq>$+Z2`?{+LFu*FQZc58QcdO<0-D8e6k-gv3( znq*KR&-a?aq)!u)4;~Sr&)+M2ir+-Isa$AC`kv%SUzib@NU-FmXMzn z#`c47YeR^4dFUiNEYn?J9SB3r4)LscX4m`VaZ(pCBM-Y;K=R>a}9q)zS_mCfvE5E7-*+418_b4rLiAuz!Z)A8qyn7LmJyLC--$wmX0w zON{P0TNT#_2*!KT(x}Nz+1K&7--Lz-T`X((fIAL);#_KO-fWp=l{sg+(y8KydDZ}E z*t|kB7nifCpK7MIov+7^RLJ3dVvHJc02WYg3a|`%u#n!8!K+Bv+LpZ8p=2;}su*(+ zJl*t8BM`5m2SN1PJ|mdlA~_Ap<6pMSXT~Akjex7@)Ya@z=ebltQ(=+>&MyMIZ4q<= z8W;3Yc)h>*LvBA*ED#*8apYG{z3ul!?g3`vIS{m}Kd@0IX>#2G>2h($bSwTNSixtn zDxKY+63hM-D@4XBG|qPNKQ74pja+!HT5W?SYD(1Cg>gG>T)Fm6uOM zK&L0q)iLhfWE)1RiH6L+|1q$Ae~J4&1pMT%{dh^AakDTqHl{(2O8fgt3+_abgXTz? zEA`_AcBWd2(>s{4OZC9Yv^y!V|2Dhr>IJarYgpeusnaD__KSM`^^s$zytY}Pk@cKQ z^@2u6u~-!kpUXEhZKk1g`u+@_Ol^fkV`9;Uwe~5}HW}Ww<97Tg#w*j5uuu-PT#`BK zqBcRwC>D;FKswz7O$Hnt_7$FB*hbxzb z^v^=Qi7>!TvADKqkUA2{t0Px-&HAr9n$qtynH*z8lH0$Y6uW;TM9AM7$X#?0tf`6U z0=Ov4uj}p{o(L3y@|~KmeMzBiY_R+rks6)-^Xu3{UQ55}%FHQgQuu>er=ltp;&%|# ze;C%K8W4bX1={QCJOiCTYOJeN=%T@T{Q~S0RXiO2I*v$5gK^PWj3Vk71@(=7Jw+Ia z?{<4#Lw0l_@fsf&hKeZf8>`N=SpC}+KfcjbSnc^o^!>wcW39pJH}AK%v8%~Zv=pY1 zuyn~(@*$r?O?+eldw^+tez+}e{@?)v$nyn(Kuust)2#fm zU>9}?QJ1T1 zd)i^Tyiw8|*5!7Z^mTi%#;DL#1G?k;x!1L|H7q?}kG-RlY%PfrMUAw`>Bw7IHBfH4 zAUD@DXE33;cj82sC-ehu;JyEAtbR7EE$|A1% z1jG3t1E!NoaJxUz=AZHCsd?toMIKodsK4T@$WxcP(0^xEG3Bp*RG0r^VeJ0u(P4cXtxpg1dWh_u{U_ zPu}mG>pJ-X$0J4kwp|$jy|t3zOE=l$ z7LV@LsckO1^cByqSxEyGTGUtHMC~B6sQwnN0ruwRM+=R%H}5CDo0*0gx!BP*{2U<; zBI2dCKwXepjjpQN0yG%wnkK&YMlxqAE*be9T7|JOJNP*oj+e7Mm@2?pN^z;8DIfu% zcWuFSae`?l6h}^VHk z8nYV2%2>14tMGdNaMc;_bKHwUTqRxF(zL7_Gn#(R7FC;Xwqu%jZEkNsKUOd|GNq)# z;4b3*g3$i*q~`rEV?hKQ_%uRZclba-YBu(Rb-&H~_2~tY z8{l(wbm3=7$&8;l3~LCsFmJ@oQ>ZJc+QHTQ?>)D}b@_-KRAdT26iX2TsWLL3E_pCS zv4`VS!l4OoN3aLcdWB3UC{4y<8-HXz#%(_;`$|bhVyf=3KtuxVk!F{UUX<5FTNNbJ z^ZcsRDIvsU6-`n>6p~kfJ5!`Kf=z)TW$HX1jwYyb%aX{FhX-{?!fdU+ z;N>TsFS|Y%!B8BlKlcPFtNe)7*Ds=if(Qqb*;1OK-Bf_Bl4q4*Z(8?SzHc7NjCMp$ z9=oH-TOneX@WoQG>W$B=^4a%xiJPqw7UrYyu?oB+p&xd82XD<9(^xsT+-`g-Q)WN& zWyMWL(v)y7V|-FQn^Dy4vHFR~hu0}LfbK-BAvn8}<#&50z1m%J&-S}}xI#m5d`_f6@Pbu7G^jV8|8sB(1MR*>k>`-jGFp&9NwMPqu;!lHso zdRn+AUVh&fe%Futmk;b!RlO`rSR9p=<&2=V4FQVdi}3g$*bg5mOS1OM2U=SGYIi-a zA6VFP64*7_HxFqz0h@qahC*8-H zK{-2^Y_Yn&n+uMU4szkyvV~IlL?PG^i;YCY+ z!exPws;ST?CAalo$HX%mV7BR#yj`O>Ts0QC6>r<#PFZUP-Ic*kk^Iia#cHrIxf1mg z2tpOPkm7eeCE>XeZZ5qX=5)n0#MDlNBZL2#E%Cad@oczoG8Pl+bT?`m#$H=f*HOnJ&^Oa8u>lB(=~T-Z!bMoyVSnw`c!X9;;&rNHvd?K5rEsVvzJ zf(66+#w{^Hj@$&{2S7N@Oxt)OU0kPG>O$gu5r;m~HUqRg^}`XgfZ=aO)mC#hlf5ui zrsupaB&u}vO{hU0q=XFq(h?!#<4G8ynA`&gj6aWsQ^)#RH-}=Uh|!W0tA_ngBuF)u zez{b+fHn#X^<;}~PANXStG6l>8tHdP16`>Z5Pl0(@Om8>5^psnGNeCq#boD(CKGz? zCu^HHSj%Z%JS5x&&E4eJa`6ia$ngOP2IgMWe~a+Gp9MZcLY)|sQpyim?P3qB?xhZn z%MQ)h2w$@=0&`P6DQzkj>rKvWO&)^1e|0O0S_X5kj~`6tB3Cb>gT211aSG$R>#)OXszGAND2D|%_~o!ZDWH1)ey3|V8>B2-aMCvJe%qKsD+@6 z!U!7?W`K<>0>3(ycBe{GBR2Ee`IZNJ;s`64@*+%b{TBr}x@L4dVuSf->r0=7tm-35zDaGe;3z?+A3wQ*Cb&-GGZ_70`uriQDDcgN7!xL2Mb zJb_V#9+&B?NWL@RWlyejz=9h4gdba)Ugx#E@mMKhlc@Sg&7b zuV-b3v>fEn$}WmHT#Kp4EzkYq?-Aq^X(Z+~vNE-0C}al7Q6y#Q6Q0w>Y<8 z&PYBm@=vZV&N~Tyrh;qJM&lMUt4u| zP&EjWkColyP}e2$tzpZb%<0`jjK;(rcu8TN#Z*}LsPV9RV@?*+(v#BXRQ>bKey##? zfA;ji5=|%uIE&pxBkT6JE}n?VmL{aa3eokZ5tI2IQm_ z0W~Nl!eio6wH6ai>R8!U4wN6dQH3Y-@riN+jZ)}jRZi=dndsHIH>`vN9sVwbcDwJ@E5YV&zBB4spNx! zK;Jv_g!1`_F+71}?Po99kDU|J!4cvq*nBox8^n`0@i4Nq_^)dU9-crDCt z)stFWO$JZt!QKs?^7(6Vs|WZKiEumH)+9v5GR!PF6bns&4~~(HW4d`{+4IY zFw=y}r8;uv@jULV+U=R8)JY~e>lwfF6M!#=E>;<^ z5*8M2fBqJr{@x9=@3-KFjxDg0P8`{CX`@(LuwshJ?v~4D{oLn!MVj1C*Kv;=a_0V4 zGf{Ai{Uz2vZ;Ks|>67fR;#t4%;F@Jfn{=2i!Ate4ahKv28(UR1xJpSGdSUiU$h6NZ z7-{$Svq1w;7p;5v+W7eRJhn{ehcFQHqgcl9Bt0jg5sSqU20Y&#kl_B8iP&eoZCHc)g8umiABJ z5vQ7-+pAHC-ggYHh`uaT*NKsiN~ypUqW7Ul$>$4P#`h7$x<{J(b~&y%F?Mz5st54L z)TDR1FqQ^J0aEHh-r9t_Oih18bJ?L!I!!WGq(;-aY>zg=pUPsfV_u6ijrg?YL+ zuxm6bX!;+tf%>ZV!vxv;q(a|;LznHKGPIT<%_#)wK zt;||ZVwkiQ3A)@K0+BxUK3_B!=7yzWVqvxFaMCo%d=h>^@mdg##P^C*V<;v|j45>} z_Uh-_0~=k?E37C7Y{9ecZU#cjlu#>%QC8iugeIy;GQT7Y#kG~ok21i{>2C6>CkWq->j-I}ol>2hW2vOHVe zIcj#XE2GfFD4a(1RL=tICjxA!XL0EEI3mBQNY`ZCVoVcED1|8Cp&Y32MBA_;hZeNo z6u<#$T^%$OPYYP#vF>9c{IiMUy%ys0S;yDXEL{ zgfU{>7&^Ys*Q)pYt@l3J8HkZkRS8t&X$x3_-L_@gNZDFL%d!Hh4x?8LnGM3PBUH90Fj9@x>>B{RVK8E_f~Z+);9YJoQ~G@vV*%)3boNFQ(&6WWL!47>k5xqBKvG+1_(Dc!D#X^9C}KGTNc z7}%ap(wUOs@FT!c!08;?gl=N-&r$S#=?>I2FjKweeUMFWw;EO5aj%ELY>43^%QwbK z|FzYj=7R^8&6KmYqYG_ktVnGvQ=1ly_=$Amf0KV>7x5EIvpq%gp> z7$MDoKwE;Znid_F7crW>PxSEo8t$Y{IzSd4(_Kw*>USx0#pktrvwn!p3*QE^>4+yW z(nLPLoAYIfGSssHQ%xCm8svcLKdJZQ{Zd!iBsOHZvy`g&u-PL8dLZn~ak7SgWAjA2 z=d1BFGFu$=SU{TtQ?H%|*s$msi2k)m{KUNeAyFxA;ly2q)j4k5Sy5Ht?AwK6Nl{XV z&eAV+E%c>lyXi$s^Ps3B4ZP1s1fO=eQcefVYO`J&jh`Fece!&P$BoaCYJ8h<_w;`X zY6NA4Ecl(y_qa~L?Q6Ivwa8l_#>MJmo$B?NB}7$`uxh=owur2uoY{s>+_Y_WyA^mY*EvVDpAf7JJ=jAtUyTEp!U<(FMUV~(1pD?B}%aEk1;h7YWByegJ45Zb{c zxl^7{!uMpq8Q1&2f6ZLvSYqKdZy>l6wyzy?L3)l~tg4{vq~3@rZcan`$bTHxV+Ql= zN+U>jXqQP1MKF1y=fzM>)#b#?WodXob@4Gd#~FWi4DMzpgXFKISc?06)9QW*WczQ` zJaCEM@F2S|hXMh8$MShU#XBi6kc{<>jG$GQw;hfgIO;n~BHrx>+Am}fQmeRkXV`9+?o za-$6j(Ub}lI!#*seXZs?KAsI==&2_S(_w8reRwuDGByr;0~@~{C*IuL5LA4ULbMcn zUY<$`_^TZtC6MlmemarI9`0K%|Cow}mj`XOHuJ&Gw=fsA0ue|ll`E-_5O^)7GIoig9d7-F63i|-FMH9a z{NA58jeo{a(UufJ1^4esrCjYAw3se>A5jFaeG~L6LGHzj-5Z9D>uAlZh;H0Esi`{} zIRjV|J71oM>LlW~lMud=tE{N8AVw`)jRIJ76s4&}I6#L$PYPHH<*>eOz7C^{ z8|^9s?`tm8G-W*A0$_CD%TVCBx(92kBa^*(I|RRkQ3|RKddOU-iZJn`*4dn?4(y&+;DSWligO{1gN$NTY+`e(jOySln6 z`){Kgd&wcV$s@SEK`j_#sw9W-=6IGBAg>yw1{EuzHJ|jfWcIb$_-jqFbqDLFzF;jK1d^ro#_oJ9Yy^^ARLK>u7Cik`6d5RZ)`p0i1l@ z{`h9f#4(wv$o!O-*^*oUWmNlOY*d!*H+^L%+dwo_)YMb5gCduoJLyu9p@gRrht@S7 zEJ0G#?x$n8Ucr4S#Mh{yUGMXw(%gXK<9V=c&35>t|I=(PyZr2gJaD71Q>U0w3ikeM zm?z6n>eNs^{rkZHIrfuyBCmQ6dAw=rPHw0y{WhH_rTB4SA!rBfV6>NqO}h z0q^OG(i(q=5E>B2Eoi9{aK}}3(7aS|!|ThUs>3AmwW|!|_3EufH-9uu?l! z(-s*^627h-h`oEu>m&eUWY5uE%S$yLtDJK2yQ2+h z9f7Z3NHO8-=WQ%H57qL!VLF=Xe%c5sMtKQ*!00@^dCUoXg05wHdiquG;JEDa=F7w; zs~oVz%0XjwG*a`wy=O6B)$pLL_dc!_%aCPSs!GaR1g6QLja>~U2|Et+*O9`VT-Ue+@WfdIOXp(`5bIgPV zen8vAU{D1Sr$n zPX8Kr&8jiee=7QA~=b8hsBaJh?P3CTZusjuafWeWdjGo2kkI`1k=aaT-9@?1YYFAXuj zO#f?fG5eo+x6KqWX+z{fpqji+mU4MMK${-4J0j&oYBzs^o;HO0F+W=BXT>IRJ@;5y z5j;+ebbDEp@zwL|lyRru;}KZ9x(di@iVD}7HenG2`b_yhn^~6(O+}S5vQD#M+J=G! z(d4If`KXoNEa4hPe!yf)ql@MuYiSep?COSFeZr&T=40Ny=zP6jRc?pIu1vfXzdhQI zOxTd?N54_my#JQ+T*)9+o-D6~j?O90;w7K?6XP~k)saC~4;Wl7FEwA4Gznurchd9g zc-f8zh)aET_ws(4t?Nj(NH}zHDmjuQt7?#aJL zmFh%{sRLf*x9)&~hC1j7mh=jA>laS_VC*J0i=fJis;eOrN9KJ*=V|0FHs5VFq3^tZ zOG$Tx_&{^sqS8{9nTR}>7nKzl;ASPPqrb&cyzyx!Asy>>NL0{x`ZU%QY&JEb7kl*d|qHX0UwCr4Rg z&-m+VXGYUASP-))^9erX8H#hg}3iJnkP8KS0wX(vSMBn~>=NlIh5Rmh>!ixmH zx-oHmu{FgKN}sIZ0Sa<=4NGo=`D15~v|ReO>qG$fj_8n>#5{vI-^s|Kh>M%B8G+$k zo(SLYveW)DwS$)Xs^}D*Zk~VpBtbw!#xN5~(huJ~@txiJZ!;C?>t^j48l-;Y`7uwy~t z;S&0bIxpeQrka}R81KWYtE-!di3#m=N0SkmkN!!-XIgXEN;L8JNg57@;^Vf$qN& zvW6+qCg&V>iNcRp>u^_WG0b;A2QC6(FWtiXolQ{BQ6~&}nvd)(Vq;=sgF|vmJTG?c z6U(V#+=fn1Pm>oG7E+2tK992zJVS}QY>M+*}!^e7vG~v7ZwmHF(FvESx!kcOE;(v9x`q#6|vN)Sj!k_z& zn(Y{>RxY)K9|V3Udgz8e0e&$PI!BjIn6`Oa{dDU_U}*2}W~-kRDzq~*gS(kdL^S=5)GpSJx@+_dU@MXsGSuw(LwD# zk6LUC+R-WjOKrP7);6V|^_R`GM*xPE1E?t-QdE(EgS2n#X5S0#@;rkF%nI#%e3(0+ zJBbQ(Afu%0bh_D}2-qjnx$Z&!xQ9D~a`=trEAYR^J4H?!IKAAPnTBXPcHGS7ZrZ%> zOw~E0Jh7v1Or|h#8_K#(R9U3vCBw|ko z+WrD{E6{Y{6HOhi&YBE*4nOsYl$5i?3j2$1h?)v?+D)?Ww}cILtNM$7{I}R zL$X?(ipT4}V~{kckv(5@!;1^8lgEXUxwig^u9%{mmIa4&^7A^-I#CN;Y^%V@D0F}ttc=yW6)U}TW|3TSVZ z@Go5%Z{h}NTzB%n-<)1U?O*hl2Ld^=Jt>j`y`^rTBe8;_;w9)@gLyu2!lJ9EHWzC9 z#KKsli^skf9hQ-td_+;Ilo{V>aBDvPLG}_gKBl0EsOD#ZxBWo%h7Ef)T#wB1@X@cu zbdJ>)A)>3YY(<|aF=s%fHJccMzQ!pwDU{90;ozNBRxPoPz9nJQzBQ;>DsRYtxx-tJ z`7{85cfKf_!8~){*sY`E=v+Tois>GFz<}gpAYfR0pog;Sm#o}=N)@WY9wP7`-v9W# z9PoU97QF?EMoOW|BPJ#$xsyzU3c2pTzi~b|;LvP!!1iDDgX-&Qc3`eJ5c*Azswv*D zYSVN#tdPccL7;hk#kT;>GBx(m=sq=`#v+OJ(B^814wZC=5FPiGaScaV|9W2ThZf!{ zm4mn;ZC2@I2c%EdyR&W7pJFfKVS!M<3cHWJduX2kFR8hzPCYkwJ@f|R2{T4QU)%Qs`hN-w9*&f&3s6sy9L-bk4Tsi3*YX z!a|GXPC+M61|GxeDegxTC*u~PNR2BeA5F%7$rBV}Ea#?V-g=4O&8HOq;E1V~d>S^o zi8pMtG#It5xOcs;>~DW4&=HG%);HAOoqFUi_|#e@K<$ui{1X!!yLM9N*Fj;A)KrM% zIRddi8&R~s|L$N60O}AfPq#ug;o-NYRw^r@$bD!6pn32Rh_k6n{8iLfs^>#)M(*`;jg<= z#6%Hy_ilAuz}w;BOVZ}ub4B`_iMcrq1%3Th!u>JHTtRncj^4Gj@Ig*KsE^>*c-C?j z8K8uuw%QTzrC{2T?V07;gJEvZBwxB7b;&-`i86AFm z!sT|tFmadAnxl;Zh0;(0ce%(x&B~c1ELAF=y}JPnRo6pi2BJf*D*+6#Q?;!YS4tYE zvfnli8a&S*g8yK{uhp9#+c}%&U}9p`=jX#x?jvz3hlHHV7OSNqBO&QYOH1!mOgl8X zTKuXg!9hR0~UD|$oowt(IL$}NPNo>=*5;ee=NH+8R{xSiF&iTYR!$0mG!RDY{~y( zU^a{%mPc6X;*WXW$EN7rlZ73OO^P>kAu0ZyeZ}b*4G9U^t9EJl5(kS)?mHzZ%sQ{A zXh42_ef?%oeP+0Wm6cQ98h^|$80aC40>w`4^!kt8YxlhM)QIPyPNLD#(8{u`>Rl>k#Hc zU&5zAFbQ^sYj}*5MG8kLn|*C1`a6fB%u?K-3SX0puBwq>To`AJYe~K zjrg9qoQl=&ZPea$v>tYKZ6evtqYT^sd!O#_@@N#=K+ekk&v{epExQhT3nCbSuCVx! ztTyM!G8JOnV1pcQ1G^1?wc}3Bl|)c@i+7p+cXjo|io#u@i%4nN(%GXND5uU5nm;Bt zH8oYISF2!b z?oA8HWF9I)e0h1YCnkz@jg=WrPEO#(Ww@}j({gjWGUSV^Ztw4#6jz9rDRkhlRuFlJ zq6T)I(Omxv*}O-8#0_BDU>wN2$h1m@l8+Vyp@(OM3bFdC3Nh?XrPE6#yz}m*{!n0C zS(yz&M6Sk{j`)#LbSEL4sxp&!6-0DREtqi@9dD|wCcjF)>God`-SVv^_a=0|SG0c)*hxD^VLiKYs+g!nd?cY}WG1O84jd|Lq<$6&OOlR*GJIAv`YL zzD~$$E|Y%0{^-5zvT#Q-x3?!3O92Qlyk7Kh?QIW;H%H|wU(sr2JGak z;D{TIJP;B0l0WqKe}KoSHyxLfDZJT=6{=r8ZTv=ab?3S1zc_*xYS!!mH+@(D&Gjb! zLQ{|;_vWjHR$`1k2TjYsE3o0Spghay#jFKA+8Qs5!)noloe1po-o2 zspnOkHdm2d$iiH?iapDjk0sll8hswIX&7*H-u?CCv-i={-AQU12e$v-FfNvrL0WiI zmXlEAEtP!f>`BfL3MTa8LT@5w6w!9R`eyUWTR)TaFQh4b9eJl+qT}hALrcVU%PHFe z0c*qIb?1Uoyc!s??`?&^v!Cxub_P+R5dnTq$AHajV7L{7MS@Y?#K%=eGt7s9e&nP8 K->M``g8vUeXqnLf literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/images/logo-cosmic.png b/cosmic-client/cosmic-ui/images/logo-cosmic.png new file mode 100644 index 0000000000000000000000000000000000000000..620347fb573c9799fb0c4c4dd80e67a1724bca39 GIT binary patch literal 3320 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 literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/images/overlay-pattern.png b/cosmic-client/cosmic-ui/images/overlay-pattern.png new file mode 100644 index 0000000000000000000000000000000000000000..72342b4ad91626ea8a51f0d0a73f7764cd12f6af GIT binary patch literal 14969 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 `~qvL`5gBZfyzg2xKY%Zl{!BU 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

iYd>OE%htbB9&ANnFDrK;sUh$f~|cJ{)qso~FuF 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 +G| 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 )cAX4FA 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�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 literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/index.jsp b/cosmic-client/cosmic-ui/index.jsp new file mode 100644 index 0000000000..5cdf5825a6 --- /dev/null +++ b/cosmic-client/cosmic-ui/index.jsp @@ -0,0 +1,1811 @@ + +<%-- + 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. + --%><%@ page contentType="text/html; charset=UTF-8" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> + + + + +<% long now = System.currentTimeMillis(); %> + + + + + + + + + + + + + + + + + +

+ +
+ + + +
+
+
    +
  • 1
  • +
  • 2
  • +
  • 3
  • +
  • 4
  • +
  • 5
  • +
  • 6
  • +
  • 7
  • +
  • 8
  • +
+
+
+
+ +
+
+ +
+

+

+
+
+ +
+
+ +
+

+

+
+
+ + +
+
+
+ + +
+
+
+
+ +
+ +
+
+

+
+
+ + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+ + +
+ + +
+
+
+ + +
+
+

+
+
+
    +
  • +
  • +
  • +
  • +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+
+ + +
+ + +
+
+ + +
+ + + + +
+ + + + +
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ +
+
+
+ + +
+ +
+

+

+

+

+ + +
+

+
+ + +
+
+
+ +
+
+ + +
+
+ + + + + + + + + + + +
+
+
+
+
+
+ + + + + + + + + + + +
+
+
+ + +
+
+
+ + +
+
*
+
+ +
+
+ + +
+
+
+ +
+
+ +
+ +
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+ +
+
+
+ + +
+ +
+
+
+ +
+
+ +
+
+
+ +
+
+ () +
+
+ +
+
+ +
+
+ () +
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ + +
+
+ () +
+
+ +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+ +
+
+
+
+ + +
+
+ +
+
+
    +
  • 1
  • +
  • 2
  • +
  • 3
  • +
  • +
  • +
  • +
  • +
  • 4
  • +
  • +
  • +
  • +
  • 5
  • +
+
+
+ +
+
+
+ +
+

+

+
+
+ +
+ + +
+
+
+ +
+ + + +
+
+ +
+ + +
+
+ +
+ + +
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+ +
+ +
+ +
+
+ +
+
 
+ +
+
+
    +
  • +
      +
    • " + class="traffic-type-draggable management"> + +
      + +   + Edit +
      +
    • +
    +
    +
    +
    +
    +
  • +
  • +
      +
    • " + class="traffic-type-draggable public"> + +
      + +   + Edit +
      +
    • +
    +
    +
    +
    +
    +
  • +
  • +
      +
    • " + class="traffic-type-draggable guest"> + +
      + +   + Edit +
      +
    • +
    +
    +
    +
    +
    +
  • +
  • +
      +
    • " + class="traffic-type-draggable storage"> + +
      + +   + Edit +
      +
    • +
    +
    +
    +
    +
    +
  • +
+
+
+
+
+
+
+ +
+ +
+ + +
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+
+
+
+
+ +
+ +
+ +
+
+
+ +
+ + +
+ +
+
+
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+ +
+ +
+
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+ +
+
+ +
+
    +
  • +
    +
    +
  • +
  • +
    +
    +
  • +
  • +
    +
    +
  • +
+
+ +
+
    +
  • +
    +
    +
  • +
  • +
    + +
    +
  • +
+
+ +
+
+ +
+
+
+ +
+
    + +
  • + +
    +
    + +
    +
    5
    +
    +
    + + +
    +
    10
    +
    +
    +
    +
  • + + +
  • + +
    +
    +
    10
    +
    +
    +
  • + + +
  • + +
    +
    +
    200
    +
    mb/s
    +
    +
  • +
+
+
+ + +
+
+
+ +
+
    +
  • + +
    +
  • +
+
+
+
+
+ +
+
+ +
+
    + +
  • +
    +
    +
  • + + +
  • +
    +
    +
  • + + +
  • +
    +
    +
  • + + +
  • +
    +
    +
  • + + +
  • +
    +
    +
    + + +
    +
    +
  • +
+
+ +
+
+ +
+ + +
+
+
    +
  • +
    +
    +
  • +
+
+
+
+ +
+
+
+ +
+
+   + +
+
+ + +
+
+ +
+
    +
  • + +   + + " + view-all-target="zones"> +
  • +
  • + +   + + " + view-all-target="pods"> +
  • +
  • + +   + + " + view-all-target="clusters"> +
  • +
  • + +   + + " + view-all-target="hosts"> +
  • +
  • + +   + + " + view-all-target="primaryStorage"> +
  • +
  • + +   + + " + view-all-target="secondaryStorage"> +
  • +
  • + +   + + " + view-all-target="systemVms"> +
  • +
  • + +   + + " + view-all-target="virtualRouters"> +
  • +
  • + +   + + " + view-all-target="sockets"> +
  • +
+
+
+ + +
+ +
+
    +
  • +
    1
    +
    +

    Set up the network for traffic between end-user VMs.

    +
  • +
  • +
    2
    +
    Clusters
    +

    Define one or more clusters to group the compute hosts.

    +
  • +
  • +
    3
    +
    Hosts
    +

    Add hosts to clusters. Hosts run hypervisors and VMs.

    +
  • +
  • +
    4
    +
    Primary Storage
    +

    Add servers to store VM disk volumes in each cluster.

    +
  • +
  • +
    5
    +
    Secondary Storage
    +

    Add servers to store templates, ISOs, and snapshots for the whole zone.

    +
  • +
+
+ + +
+
    +
  • +
    1
    +
    Public
    +

    Set up the network for Internet traffic.

    +
  • +
  • +
    2
    +
    Guest
    +

    Set up the network for traffic between end-user VMs.

    +
  • +
  • +
    3
    +
    Clusters
    +

    Define one or more clusters to group the compute hosts.

    +
  • +
  • +
    4
    +
    Hosts
    +

    Add hosts to clusters. Hosts run hypervisors and VMs.

    +
  • +
  • +
    5
    +
    Primary Storage
    +

    Add servers to store VM disk volumes in each cluster.

    +
  • +
  • +
    6
    +
    Secondary Storage
    +

    Add servers to store templates, ISOs, and snapshots for the whole zone.

    +
  • +
+
+ + +
+
+ Zone Configuration +
+
    +
  • +
    1
    +
    Public
    +
    Configure
    +
  • +
  • +
    Management
    +
    Configure
    +
  • +
  • +
    2
    +
    1
    +
    Guest
    +
    Configure
    +
  • +
+
+ + +
+
+
Add Resource
+
+
    +
  • +
    Pods
    +
    View All
    +
  • +
  • +
    3
    +
    2
    +
    Clusters
    +
    View All
    +
  • +
  • +
    4
    +
    3
    +
    Hosts
    +
    View All
    +
  • +
  • +
    5
    +
    4
    +
    Primary Storage
    +
    View All
    +
  • +
  • +
    6
    +
    5
    +
    Secondary Storage
    +
    View All
    +
  • +
+
+
+ + +
+ +
+
+
+
+
+
    +
  • +
    + Alert 1 +

    Alert 1

    +

    Alert 1

    +
    +
  • +
+
+ + +
+
+
+
+
    +
  • +
    + Alert 1 +

    Alert 1

    +
    +
  • +
+
+ +
+
+
+ +
+ +
+ +
+ + +
+ + +
+
    +
  • +
    + : +
    +
    +
    %
    +
    +
    +
    +
    +
    + + / + +
    +
    +
  • +
+
+
+
+ + +
+
+
+ +
+
    +
  • +
    +
    +
  • +
  • +
    +
    +
  • +
  • +
    +
    +
  • +
+
+
+ +
+
    +
  • + + + + + + +
    +
    +
      +
    • +
      + +
    • +
    +
    +
  • +
  • + + + + + + +
    + + + + + + + + + +
    +
    :
    +
    +
    +
    :
    +
    +
    +
  • +
+
+
+ + +
+

+ +
+

Schedule:

+ +
+
    +
  • +
  • +
  • +
  • +
+ + +
+
+ + + +
+
+
+ + +
+
+ + +
+
+
+ +
+
+ + +
+
+
+ + +
+
+
+
+ + +
+
+ + + +
+
+
+ + + +
+
+ + +
+
+
+ +
+
+ + +
+
+
+ + +
+
+
+
+ + +
+
+ + + +
+
+
+ + + +
+
+ + +
+
+
+ +
+
+ + +
+
+
+ +
+
+ + +
+
+
+ + +
+
+
+
+ + +
+
+ + + +
+
+
+ + + +
+
+ + +
+
+
+ +
+
+ + +
+
+
+ +
+
+ + +
+
+
+ + +
+
+
+
+
+ +
+
+
+
+ +
+

Scheduled Snapshots

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

 

 

 

 
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cosmic-client/cosmic-ui/lib/date.js b/cosmic-client/cosmic-ui/lib/date.js new file mode 100644 index 0000000000..e5bf9cfac2 --- /dev/null +++ b/cosmic-client/cosmic-ui/lib/date.js @@ -0,0 +1,125 @@ +// 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 +// 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. +Date.prototype.setISO8601 = function(dString){ + + var regexp = /(\d\d\d\d)(-)?(\d\d)(-)?(\d\d)(T)?(\d\d)(:)?(\d\d)(:)?(\d\d)(\.\d+)?(Z|([+-])(\d\d)(:)?(\d\d))/; + + if (dString.toString().match(new RegExp(regexp))) { + var d = dString.match(new RegExp(regexp)); + var offset = 0; + + this.setUTCDate(1); + this.setUTCFullYear(parseInt(d[1],10)); + this.setUTCMonth(parseInt(d[3],10) - 1); + this.setUTCDate(parseInt(d[5],10)); + this.setUTCHours(parseInt(d[7],10)); + this.setUTCMinutes(parseInt(d[9],10)); + this.setUTCSeconds(parseInt(d[11],10)); + if (d[12]) + this.setUTCMilliseconds(parseFloat(d[12]) * 1000); + else + this.setUTCMilliseconds(0); + if (d[13] != 'Z') { + offset = (d[15] * 60) + parseInt(d[17],10); + offset *= ((d[14] == '-') ? -1 : 1); + this.setTime(this.getTime() - offset * 60 * 1000); + } + } + else { + this.setTime(Date.parse(dString)); + } + return this; +}; + +//***** vmops (begin) *************************************************************** + +/* +This is a hack/temporary solution that lacks calculation of Daylight Saving Time. +We'll fix the problem by getting datetime in a specified timezone (including Daylight Saving Time) from server-side in next release. +*/ +Date.prototype.getTimePlusTimezoneOffset = function(timezoneOffset) { + var milliseconds = this.getTime(); + var s1 = new Date(milliseconds + (timezoneOffset * 60 * 60 * 1000)).toUTCString(); //e.g. "Tue, 08 Jun 2010 19:13:49 GMT", "Tue, 25 May 2010 12:07:01 UTC" + var s2 = s1.substring(s1.indexOf(", ")+2); //e.g. "08 Jun 2010 19:13:49 GMT", "25 May 2010 12:07:01 UTC" + var s3 = s2.substring(0,s2.length-4); //e.g. "08 Jun 2010 19:13:49", "25 May 2010 12:10:16" + return s3; +} + +//***** vmops (end) ***************************************************************** + +Date.prototype.format = function(format) { + var returnStr = ''; + var replace = Date.replaceChars; + for (var i = 0; i < format.length; i++) { + var curChar = format.charAt(i); + if (replace[curChar]) { + returnStr += replace[curChar].call(this); + } else { + returnStr += curChar; + } + } + return returnStr; +}; +Date.replaceChars = { + shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + longMonths: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + shortDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + longDays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + + // Day + d: function() { return (this.getDate() < 10 ? '0' : '') + this.getDate(); }, + D: function() { return Date.replaceChars.shortDays[this.getDay()]; }, + j: function() { return this.getDate(); }, + l: function() { return Date.replaceChars.longDays[this.getDay()]; }, + N: function() { return this.getDay() + 1; }, + S: function() { return (this.getDate() % 10 == 1 && this.getDate() != 11 ? 'st' : (this.getDate() % 10 == 2 && this.getDate() != 12 ? 'nd' : (this.getDate() % 10 == 3 && this.getDate() != 13 ? 'rd' : 'th'))); }, + w: function() { return this.getDay(); }, + z: function() { return "Not Yet Supported"; }, + // Week + W: function() { return "Not Yet Supported"; }, + // Month + F: function() { return Date.replaceChars.longMonths[this.getMonth()]; }, + m: function() { return (this.getMonth() < 9 ? '0' : '') + (this.getMonth() + 1); }, + M: function() { return Date.replaceChars.shortMonths[this.getMonth()]; }, + n: function() { return this.getMonth() + 1; }, + t: function() { return "Not Yet Supported"; }, + // Year + L: function() { return "Not Yet Supported"; }, + o: function() { return "Not Supported"; }, + Y: function() { return this.getFullYear(); }, + y: function() { return ('' + this.getFullYear()).substr(2); }, + // Time + a: function() { return this.getHours() < 12 ? 'am' : 'pm'; }, + A: function() { return this.getHours() < 12 ? 'AM' : 'PM'; }, + B: function() { return "Not Yet Supported"; }, + g: function() { return this.getHours() % 12 || 12; }, + G: function() { return this.getHours(); }, + h: function() { return ((this.getHours() % 12 || 12) < 10 ? '0' : '') + (this.getHours() % 12 || 12); }, + H: function() { return (this.getHours() < 10 ? '0' : '') + this.getHours(); }, + i: function() { return (this.getMinutes() < 10 ? '0' : '') + this.getMinutes(); }, + s: function() { return (this.getSeconds() < 10 ? '0' : '') + this.getSeconds(); }, + // Timezone + e: function() { return "Not Yet Supported"; }, + I: function() { return "Not Supported"; }, + O: function() { return (-this.getTimezoneOffset() < 0 ? '-' : '+') + (Math.abs(this.getTimezoneOffset() / 60) < 10 ? '0' : '') + (Math.abs(this.getTimezoneOffset() / 60)) + '00'; }, + T: function() { var m = this.getMonth(); this.setMonth(0); var result = this.toTimeString().replace(/^.+ \(?([^\)]+)\)?$/, '$1'); this.setMonth(m); return result;}, + Z: function() { return -this.getTimezoneOffset() * 60; }, + // Full Date/Time + c: function() { return "Not Yet Supported"; }, + r: function() { return this.toString(); }, + U: function() { return this.getTime() / 1000; } +}; diff --git a/cosmic-client/cosmic-ui/lib/excanvas.js b/cosmic-client/cosmic-ui/lib/excanvas.js new file mode 100644 index 0000000000..c40d6f7014 --- /dev/null +++ b/cosmic-client/cosmic-ui/lib/excanvas.js @@ -0,0 +1,1427 @@ +// Copyright 2006 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +// Known Issues: +// +// * Patterns only support repeat. +// * Radial gradient are not implemented. The VML version of these look very +// different from the canvas one. +// * Clipping paths are not implemented. +// * Coordsize. The width and height attribute have higher priority than the +// width and height style values which isn't correct. +// * Painting mode isn't implemented. +// * Canvas width/height should is using content-box by default. IE in +// Quirks mode will draw the canvas using border-box. Either change your +// doctype to HTML5 +// (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype) +// or use Box Sizing Behavior from WebFX +// (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html) +// * Non uniform scaling does not correctly scale strokes. +// * Filling very large shapes (above 5000 points) is buggy. +// * Optimize. There is always room for speed improvements. + +// Only add this code if we do not already have a canvas implementation +if (!document.createElement('canvas').getContext) { + +(function() { + + // alias some functions to make (compiled) code shorter + var m = Math; + var mr = m.round; + var ms = m.sin; + var mc = m.cos; + var abs = m.abs; + var sqrt = m.sqrt; + + // this is used for sub pixel precision + var Z = 10; + var Z2 = Z / 2; + + /** + * This funtion is assigned to the elements as element.getContext(). + * @this {HTMLElement} + * @return {CanvasRenderingContext2D_} + */ + function getContext() { + return this.context_ || + (this.context_ = new CanvasRenderingContext2D_(this)); + } + + var slice = Array.prototype.slice; + + /** + * Binds a function to an object. The returned function will always use the + * passed in {@code obj} as {@code this}. + * + * Example: + * + * g = bind(f, obj, a, b) + * g(c, d) // will do f.call(obj, a, b, c, d) + * + * @param {Function} f The function to bind the object to + * @param {Object} obj The object that should act as this when the function + * is called + * @param {*} var_args Rest arguments that will be used as the initial + * arguments when the function is called + * @return {Function} A new function that has bound this + */ + function bind(f, obj, var_args) { + var a = slice.call(arguments, 2); + return function() { + return f.apply(obj, a.concat(slice.call(arguments))); + }; + } + + function encodeHtmlAttribute(s) { + return String(s).replace(/&/g, '&').replace(/"/g, '"'); + } + + function addNamespacesAndStylesheet(doc) { + // create xmlns + if (!doc.namespaces['g_vml_']) { + doc.namespaces.add('g_vml_', 'urn:schemas-microsoft-com:vml', + '#default#VML'); + + } + if (!doc.namespaces['g_o_']) { + doc.namespaces.add('g_o_', 'urn:schemas-microsoft-com:office:office', + '#default#VML'); + } + + // Setup default CSS. Only add one style sheet per document + if (!doc.styleSheets['ex_canvas_']) { + var ss = doc.createStyleSheet(); + ss.owningElement.id = 'ex_canvas_'; + ss.cssText = 'canvas{display:inline-block;overflow:hidden;' + + // default size is 300x150 in Gecko and Opera + 'text-align:left;width:300px;height:150px}'; + } + } + + // Add namespaces and stylesheet at startup. + addNamespacesAndStylesheet(document); + + var G_vmlCanvasManager_ = { + init: function(opt_doc) { + if (/MSIE/.test(navigator.userAgent) && !window.opera) { + var doc = opt_doc || document; + // Create a dummy element so that IE will allow canvas elements to be + // recognized. + doc.createElement('canvas'); + doc.attachEvent('onreadystatechange', bind(this.init_, this, doc)); + } + }, + + init_: function(doc) { + // find all canvas elements + var els = doc.getElementsByTagName('canvas'); + for (var i = 0; i < els.length; i++) { + this.initElement(els[i]); + } + }, + + /** + * Public initializes a canvas element so that it can be used as canvas + * element from now on. This is called automatically before the page is + * loaded but if you are creating elements using createElement you need to + * make sure this is called on the element. + * @param {HTMLElement} el The canvas element to initialize. + * @return {HTMLElement} the element that was created. + */ + initElement: function(el) { + if (!el.getContext) { + el.getContext = getContext; + + // Add namespaces and stylesheet to document of the element. + addNamespacesAndStylesheet(el.ownerDocument); + + // Remove fallback content. There is no way to hide text nodes so we + // just remove all childNodes. We could hide all elements and remove + // text nodes but who really cares about the fallback content. + el.innerHTML = ''; + + // do not use inline function because that will leak memory + el.attachEvent('onpropertychange', onPropertyChange); + el.attachEvent('onresize', onResize); + + var attrs = el.attributes; + if (attrs.width && attrs.width.specified) { + // TODO: use runtimeStyle and coordsize + // el.getContext().setWidth_(attrs.width.nodeValue); + el.style.width = attrs.width.nodeValue + 'px'; + } else { + el.width = el.clientWidth; + } + if (attrs.height && attrs.height.specified) { + // TODO: use runtimeStyle and coordsize + // el.getContext().setHeight_(attrs.height.nodeValue); + el.style.height = attrs.height.nodeValue + 'px'; + } else { + el.height = el.clientHeight; + } + //el.getContext().setCoordsize_() + } + return el; + } + }; + + function onPropertyChange(e) { + var el = e.srcElement; + + switch (e.propertyName) { + case 'width': + el.getContext().clearRect(); + el.style.width = el.attributes.width.nodeValue + 'px'; + // In IE8 this does not trigger onresize. + el.firstChild.style.width = el.clientWidth + 'px'; + break; + case 'height': + el.getContext().clearRect(); + el.style.height = el.attributes.height.nodeValue + 'px'; + el.firstChild.style.height = el.clientHeight + 'px'; + break; + } + } + + function onResize(e) { + var el = e.srcElement; + if (el.firstChild) { + el.firstChild.style.width = el.clientWidth + 'px'; + el.firstChild.style.height = el.clientHeight + 'px'; + } + } + + G_vmlCanvasManager_.init(); + + // precompute "00" to "FF" + var decToHex = []; + for (var i = 0; i < 16; i++) { + for (var j = 0; j < 16; j++) { + decToHex[i * 16 + j] = i.toString(16) + j.toString(16); + } + } + + function createMatrixIdentity() { + return [ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1] + ]; + } + + function matrixMultiply(m1, m2) { + var result = createMatrixIdentity(); + + for (var x = 0; x < 3; x++) { + for (var y = 0; y < 3; y++) { + var sum = 0; + + for (var z = 0; z < 3; z++) { + sum += m1[x][z] * m2[z][y]; + } + + result[x][y] = sum; + } + } + return result; + } + + function copyState(o1, o2) { + o2.fillStyle = o1.fillStyle; + o2.lineCap = o1.lineCap; + o2.lineJoin = o1.lineJoin; + o2.lineWidth = o1.lineWidth; + o2.miterLimit = o1.miterLimit; + o2.shadowBlur = o1.shadowBlur; + o2.shadowColor = o1.shadowColor; + o2.shadowOffsetX = o1.shadowOffsetX; + o2.shadowOffsetY = o1.shadowOffsetY; + o2.strokeStyle = o1.strokeStyle; + o2.globalAlpha = o1.globalAlpha; + o2.font = o1.font; + o2.textAlign = o1.textAlign; + o2.textBaseline = o1.textBaseline; + o2.arcScaleX_ = o1.arcScaleX_; + o2.arcScaleY_ = o1.arcScaleY_; + o2.lineScale_ = o1.lineScale_; + } + + var colorData = { + aliceblue: '#F0F8FF', + antiquewhite: '#FAEBD7', + aquamarine: '#7FFFD4', + azure: '#F0FFFF', + beige: '#F5F5DC', + bisque: '#FFE4C4', + black: '#000000', + blanchedalmond: '#FFEBCD', + blueviolet: '#8A2BE2', + brown: '#A52A2A', + burlywood: '#DEB887', + cadetblue: '#5F9EA0', + chartreuse: '#7FFF00', + chocolate: '#D2691E', + coral: '#FF7F50', + cornflowerblue: '#6495ED', + cornsilk: '#FFF8DC', + crimson: '#DC143C', + cyan: '#00FFFF', + darkblue: '#00008B', + darkcyan: '#008B8B', + darkgoldenrod: '#B8860B', + darkgray: '#A9A9A9', + darkgreen: '#006400', + darkgrey: '#A9A9A9', + darkkhaki: '#BDB76B', + darkmagenta: '#8B008B', + darkolivegreen: '#556B2F', + darkorange: '#FF8C00', + darkorchid: '#9932CC', + darkred: '#8B0000', + darksalmon: '#E9967A', + darkseagreen: '#8FBC8F', + darkslateblue: '#483D8B', + darkslategray: '#2F4F4F', + darkslategrey: '#2F4F4F', + darkturquoise: '#00CED1', + darkviolet: '#9400D3', + deeppink: '#FF1493', + deepskyblue: '#00BFFF', + dimgray: '#696969', + dimgrey: '#696969', + dodgerblue: '#1E90FF', + firebrick: '#B22222', + floralwhite: '#FFFAF0', + forestgreen: '#228B22', + gainsboro: '#DCDCDC', + ghostwhite: '#F8F8FF', + gold: '#FFD700', + goldenrod: '#DAA520', + grey: '#808080', + greenyellow: '#ADFF2F', + honeydew: '#F0FFF0', + hotpink: '#FF69B4', + indianred: '#CD5C5C', + indigo: '#4B0082', + ivory: '#FFFFF0', + khaki: '#F0E68C', + lavender: '#E6E6FA', + lavenderblush: '#FFF0F5', + lawngreen: '#7CFC00', + lemonchiffon: '#FFFACD', + lightblue: '#ADD8E6', + lightcoral: '#F08080', + lightcyan: '#E0FFFF', + lightgoldenrodyellow: '#FAFAD2', + lightgreen: '#90EE90', + lightgrey: '#D3D3D3', + lightpink: '#FFB6C1', + lightsalmon: '#FFA07A', + lightseagreen: '#20B2AA', + lightskyblue: '#87CEFA', + lightslategray: '#778899', + lightslategrey: '#778899', + lightsteelblue: '#B0C4DE', + lightyellow: '#FFFFE0', + limegreen: '#32CD32', + linen: '#FAF0E6', + magenta: '#FF00FF', + mediumaquamarine: '#66CDAA', + mediumblue: '#0000CD', + mediumorchid: '#BA55D3', + mediumpurple: '#9370DB', + mediumseagreen: '#3CB371', + mediumslateblue: '#7B68EE', + mediumspringgreen: '#00FA9A', + mediumturquoise: '#48D1CC', + mediumvioletred: '#C71585', + midnightblue: '#191970', + mintcream: '#F5FFFA', + mistyrose: '#FFE4E1', + moccasin: '#FFE4B5', + navajowhite: '#FFDEAD', + oldlace: '#FDF5E6', + olivedrab: '#6B8E23', + orange: '#FFA500', + orangered: '#FF4500', + orchid: '#DA70D6', + palegoldenrod: '#EEE8AA', + palegreen: '#98FB98', + paleturquoise: '#AFEEEE', + palevioletred: '#DB7093', + papayawhip: '#FFEFD5', + peachpuff: '#FFDAB9', + peru: '#CD853F', + pink: '#FFC0CB', + plum: '#DDA0DD', + powderblue: '#B0E0E6', + rosybrown: '#BC8F8F', + royalblue: '#4169E1', + saddlebrown: '#8B4513', + salmon: '#FA8072', + sandybrown: '#F4A460', + seagreen: '#2E8B57', + seashell: '#FFF5EE', + sienna: '#A0522D', + skyblue: '#87CEEB', + slateblue: '#6A5ACD', + slategray: '#708090', + slategrey: '#708090', + snow: '#FFFAFA', + springgreen: '#00FF7F', + steelblue: '#4682B4', + tan: '#D2B48C', + thistle: '#D8BFD8', + tomato: '#FF6347', + turquoise: '#40E0D0', + violet: '#EE82EE', + wheat: '#F5DEB3', + whitesmoke: '#F5F5F5', + yellowgreen: '#9ACD32' + }; + + + function getRgbHslContent(styleString) { + var start = styleString.indexOf('(', 3); + var end = styleString.indexOf(')', start + 1); + var parts = styleString.substring(start + 1, end).split(','); + // add alpha if needed + if (parts.length == 4 && styleString.substr(3, 1) == 'a') { + alpha = Number(parts[3]); + } else { + parts[3] = 1; + } + return parts; + } + + function percent(s) { + return parseFloat(s) / 100; + } + + function clamp(v, min, max) { + return Math.min(max, Math.max(min, v)); + } + + function hslToRgb(parts){ + var r, g, b; + h = parseFloat(parts[0]) / 360 % 360; + if (h < 0) + h++; + s = clamp(percent(parts[1]), 0, 1); + l = clamp(percent(parts[2]), 0, 1); + if (s == 0) { + r = g = b = l; // achromatic + } else { + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = hueToRgb(p, q, h + 1 / 3); + g = hueToRgb(p, q, h); + b = hueToRgb(p, q, h - 1 / 3); + } + + return '#' + decToHex[Math.floor(r * 255)] + + decToHex[Math.floor(g * 255)] + + decToHex[Math.floor(b * 255)]; + } + + function hueToRgb(m1, m2, h) { + if (h < 0) + h++; + if (h > 1) + h--; + + if (6 * h < 1) + return m1 + (m2 - m1) * 6 * h; + else if (2 * h < 1) + return m2; + else if (3 * h < 2) + return m1 + (m2 - m1) * (2 / 3 - h) * 6; + else + return m1; + } + + function processStyle(styleString) { + var str, alpha = 1; + + styleString = String(styleString); + if (styleString.charAt(0) == '#') { + str = styleString; + } else if (/^rgb/.test(styleString)) { + var parts = getRgbHslContent(styleString); + var str = '#', n; + for (var i = 0; i < 3; i++) { + if (parts[i].indexOf('%') != -1) { + n = Math.floor(percent(parts[i]) * 255); + } else { + n = Number(parts[i]); + } + str += decToHex[clamp(n, 0, 255)]; + } + alpha = parts[3]; + } else if (/^hsl/.test(styleString)) { + var parts = getRgbHslContent(styleString); + str = hslToRgb(parts); + alpha = parts[3]; + } else { + str = colorData[styleString] || styleString; + } + return {color: str, alpha: alpha}; + } + + var DEFAULT_STYLE = { + style: 'normal', + variant: 'normal', + weight: 'normal', + size: 10, + family: 'sans-serif' + }; + + // Internal text style cache + var fontStyleCache = {}; + + function processFontStyle(styleString) { + if (fontStyleCache[styleString]) { + return fontStyleCache[styleString]; + } + + var el = document.createElement('div'); + var style = el.style; + try { + style.font = styleString; + } catch (ex) { + // Ignore failures to set to invalid font. + } + + return fontStyleCache[styleString] = { + style: style.fontStyle || DEFAULT_STYLE.style, + variant: style.fontVariant || DEFAULT_STYLE.variant, + weight: style.fontWeight || DEFAULT_STYLE.weight, + size: style.fontSize || DEFAULT_STYLE.size, + family: style.fontFamily || DEFAULT_STYLE.family + }; + } + + function getComputedStyle(style, element) { + var computedStyle = {}; + + for (var p in style) { + computedStyle[p] = style[p]; + } + + // Compute the size + var canvasFontSize = parseFloat(element.currentStyle.fontSize), + fontSize = parseFloat(style.size); + + if (typeof style.size == 'number') { + computedStyle.size = style.size; + } else if (style.size.indexOf('px') != -1) { + computedStyle.size = fontSize; + } else if (style.size.indexOf('em') != -1) { + computedStyle.size = canvasFontSize * fontSize; + } else if(style.size.indexOf('%') != -1) { + computedStyle.size = (canvasFontSize / 100) * fontSize; + } else if (style.size.indexOf('pt') != -1) { + computedStyle.size = fontSize / .75; + } else { + computedStyle.size = canvasFontSize; + } + + // Different scaling between normal text and VML text. This was found using + // trial and error to get the same size as non VML text. + computedStyle.size *= 0.981; + + return computedStyle; + } + + function buildStyle(style) { + return style.style + ' ' + style.variant + ' ' + style.weight + ' ' + + style.size + 'px ' + style.family; + } + + function processLineCap(lineCap) { + switch (lineCap) { + case 'butt': + return 'flat'; + case 'round': + return 'round'; + case 'square': + default: + return 'square'; + } + } + + /** + * This class implements CanvasRenderingContext2D interface as described by + * the WHATWG. + * @param {HTMLElement} surfaceElement The element that the 2D context should + * be associated with + */ + function CanvasRenderingContext2D_(surfaceElement) { + this.m_ = createMatrixIdentity(); + + this.mStack_ = []; + this.aStack_ = []; + this.currentPath_ = []; + + // Canvas context properties + this.strokeStyle = '#000'; + this.fillStyle = '#000'; + + this.lineWidth = 1; + this.lineJoin = 'miter'; + this.lineCap = 'butt'; + this.miterLimit = Z * 1; + this.globalAlpha = 1; + this.font = '10px sans-serif'; + this.textAlign = 'left'; + this.textBaseline = 'alphabetic'; + this.canvas = surfaceElement; + + var el = surfaceElement.ownerDocument.createElement('div'); + el.style.width = surfaceElement.clientWidth + 'px'; + el.style.height = surfaceElement.clientHeight + 'px'; + el.style.overflow = 'hidden'; + el.style.position = 'absolute'; + surfaceElement.appendChild(el); + + this.element_ = el; + this.arcScaleX_ = 1; + this.arcScaleY_ = 1; + this.lineScale_ = 1; + } + + var contextPrototype = CanvasRenderingContext2D_.prototype; + contextPrototype.clearRect = function() { + if (this.textMeasureEl_) { + this.textMeasureEl_.removeNode(true); + this.textMeasureEl_ = null; + } + this.element_.innerHTML = ''; + }; + + contextPrototype.beginPath = function() { + // TODO: Branch current matrix so that save/restore has no effect + // as per safari docs. + this.currentPath_ = []; + }; + + contextPrototype.moveTo = function(aX, aY) { + var p = this.getCoords_(aX, aY); + this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y}); + this.currentX_ = p.x; + this.currentY_ = p.y; + }; + + contextPrototype.lineTo = function(aX, aY) { + var p = this.getCoords_(aX, aY); + this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y}); + + this.currentX_ = p.x; + this.currentY_ = p.y; + }; + + contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, + aCP2x, aCP2y, + aX, aY) { + var p = this.getCoords_(aX, aY); + var cp1 = this.getCoords_(aCP1x, aCP1y); + var cp2 = this.getCoords_(aCP2x, aCP2y); + bezierCurveTo(this, cp1, cp2, p); + }; + + // Helper function that takes the already fixed cordinates. + function bezierCurveTo(self, cp1, cp2, p) { + self.currentPath_.push({ + type: 'bezierCurveTo', + cp1x: cp1.x, + cp1y: cp1.y, + cp2x: cp2.x, + cp2y: cp2.y, + x: p.x, + y: p.y + }); + self.currentX_ = p.x; + self.currentY_ = p.y; + } + + contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) { + // the following is lifted almost directly from + // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes + + var cp = this.getCoords_(aCPx, aCPy); + var p = this.getCoords_(aX, aY); + + var cp1 = { + x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_), + y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_) + }; + var cp2 = { + x: cp1.x + (p.x - this.currentX_) / 3.0, + y: cp1.y + (p.y - this.currentY_) / 3.0 + }; + + bezierCurveTo(this, cp1, cp2, p); + }; + + contextPrototype.arc = function(aX, aY, aRadius, + aStartAngle, aEndAngle, aClockwise) { + aRadius *= Z; + var arcType = aClockwise ? 'at' : 'wa'; + + var xStart = aX + mc(aStartAngle) * aRadius - Z2; + var yStart = aY + ms(aStartAngle) * aRadius - Z2; + + var xEnd = aX + mc(aEndAngle) * aRadius - Z2; + var yEnd = aY + ms(aEndAngle) * aRadius - Z2; + + // IE won't render arches drawn counter clockwise if xStart == xEnd. + if (xStart == xEnd && !aClockwise) { + xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something + // that can be represented in binary + } + + var p = this.getCoords_(aX, aY); + var pStart = this.getCoords_(xStart, yStart); + var pEnd = this.getCoords_(xEnd, yEnd); + + this.currentPath_.push({type: arcType, + x: p.x, + y: p.y, + radius: aRadius, + xStart: pStart.x, + yStart: pStart.y, + xEnd: pEnd.x, + yEnd: pEnd.y}); + + }; + + contextPrototype.rect = function(aX, aY, aWidth, aHeight) { + this.moveTo(aX, aY); + this.lineTo(aX + aWidth, aY); + this.lineTo(aX + aWidth, aY + aHeight); + this.lineTo(aX, aY + aHeight); + this.closePath(); + }; + + contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) { + var oldPath = this.currentPath_; + this.beginPath(); + + this.moveTo(aX, aY); + this.lineTo(aX + aWidth, aY); + this.lineTo(aX + aWidth, aY + aHeight); + this.lineTo(aX, aY + aHeight); + this.closePath(); + this.stroke(); + + this.currentPath_ = oldPath; + }; + + contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) { + var oldPath = this.currentPath_; + this.beginPath(); + + this.moveTo(aX, aY); + this.lineTo(aX + aWidth, aY); + this.lineTo(aX + aWidth, aY + aHeight); + this.lineTo(aX, aY + aHeight); + this.closePath(); + this.fill(); + + this.currentPath_ = oldPath; + }; + + contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) { + var gradient = new CanvasGradient_('gradient'); + gradient.x0_ = aX0; + gradient.y0_ = aY0; + gradient.x1_ = aX1; + gradient.y1_ = aY1; + return gradient; + }; + + contextPrototype.createRadialGradient = function(aX0, aY0, aR0, + aX1, aY1, aR1) { + var gradient = new CanvasGradient_('gradientradial'); + gradient.x0_ = aX0; + gradient.y0_ = aY0; + gradient.r0_ = aR0; + gradient.x1_ = aX1; + gradient.y1_ = aY1; + gradient.r1_ = aR1; + return gradient; + }; + + contextPrototype.drawImage = function(image, var_args) { + var dx, dy, dw, dh, sx, sy, sw, sh; + + // to find the original width we overide the width and height + var oldRuntimeWidth = image.runtimeStyle.width; + var oldRuntimeHeight = image.runtimeStyle.height; + image.runtimeStyle.width = 'auto'; + image.runtimeStyle.height = 'auto'; + + // get the original size + var w = image.width; + var h = image.height; + + // and remove overides + image.runtimeStyle.width = oldRuntimeWidth; + image.runtimeStyle.height = oldRuntimeHeight; + + if (arguments.length == 3) { + dx = arguments[1]; + dy = arguments[2]; + sx = sy = 0; + sw = dw = w; + sh = dh = h; + } else if (arguments.length == 5) { + dx = arguments[1]; + dy = arguments[2]; + dw = arguments[3]; + dh = arguments[4]; + sx = sy = 0; + sw = w; + sh = h; + } else if (arguments.length == 9) { + sx = arguments[1]; + sy = arguments[2]; + sw = arguments[3]; + sh = arguments[4]; + dx = arguments[5]; + dy = arguments[6]; + dw = arguments[7]; + dh = arguments[8]; + } else { + throw Error('Invalid number of arguments'); + } + + var d = this.getCoords_(dx, dy); + + var w2 = sw / 2; + var h2 = sh / 2; + + var vmlStr = []; + + var W = 10; + var H = 10; + + // For some reason that I've now forgotten, using divs didn't work + vmlStr.push(' ' , + '', + ''); + + this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join('')); + }; + + contextPrototype.stroke = function(aFill) { + var W = 10; + var H = 10; + // Divide the shape into chunks if it's too long because IE has a limit + // somewhere for how long a VML shape can be. This simple division does + // not work with fills, only strokes, unfortunately. + var chunkSize = 5000; + + var min = {x: null, y: null}; + var max = {x: null, y: null}; + + for (var j = 0; j < this.currentPath_.length; j += chunkSize) { + var lineStr = []; + var lineOpen = false; + + lineStr.push(''); + + if (!aFill) { + appendStroke(this, lineStr); + } else { + appendFill(this, lineStr, min, max); + } + + lineStr.push(''); + + this.element_.insertAdjacentHTML('beforeEnd', lineStr.join('')); + } + }; + + function appendStroke(ctx, lineStr) { + var a = processStyle(ctx.strokeStyle); + var color = a.color; + var opacity = a.alpha * ctx.globalAlpha; + var lineWidth = ctx.lineScale_ * ctx.lineWidth; + + // VML cannot correctly render a line if the width is less than 1px. + // In that case, we dilute the color to make the line look thinner. + if (lineWidth < 1) { + opacity *= lineWidth; + } + + lineStr.push( + '' + ); + } + + function appendFill(ctx, lineStr, min, max) { + var fillStyle = ctx.fillStyle; + var arcScaleX = ctx.arcScaleX_; + var arcScaleY = ctx.arcScaleY_; + var width = max.x - min.x; + var height = max.y - min.y; + if (fillStyle instanceof CanvasGradient_) { + // TODO: Gradients transformed with the transformation matrix. + var angle = 0; + var focus = {x: 0, y: 0}; + + // additional offset + var shift = 0; + // scale factor for offset + var expansion = 1; + + if (fillStyle.type_ == 'gradient') { + var x0 = fillStyle.x0_ / arcScaleX; + var y0 = fillStyle.y0_ / arcScaleY; + var x1 = fillStyle.x1_ / arcScaleX; + var y1 = fillStyle.y1_ / arcScaleY; + var p0 = ctx.getCoords_(x0, y0); + var p1 = ctx.getCoords_(x1, y1); + var dx = p1.x - p0.x; + var dy = p1.y - p0.y; + angle = Math.atan2(dx, dy) * 180 / Math.PI; + + // The angle should be a non-negative number. + if (angle < 0) { + angle += 360; + } + + // Very small angles produce an unexpected result because they are + // converted to a scientific notation string. + if (angle < 1e-6) { + angle = 0; + } + } else { + var p0 = ctx.getCoords_(fillStyle.x0_, fillStyle.y0_); + focus = { + x: (p0.x - min.x) / width, + y: (p0.y - min.y) / height + }; + + width /= arcScaleX * Z; + height /= arcScaleY * Z; + var dimension = m.max(width, height); + shift = 2 * fillStyle.r0_ / dimension; + expansion = 2 * fillStyle.r1_ / dimension - shift; + } + + // We need to sort the color stops in ascending order by offset, + // otherwise IE won't interpret it correctly. + var stops = fillStyle.colors_; + stops.sort(function(cs1, cs2) { + return cs1.offset - cs2.offset; + }); + + var length = stops.length; + var color1 = stops[0].color; + var color2 = stops[length - 1].color; + var opacity1 = stops[0].alpha * ctx.globalAlpha; + var opacity2 = stops[length - 1].alpha * ctx.globalAlpha; + + var colors = []; + for (var i = 0; i < length; i++) { + var stop = stops[i]; + colors.push(stop.offset * expansion + shift + ' ' + stop.color); + } + + // When colors attribute is used, the meanings of opacity and o:opacity2 + // are reversed. + lineStr.push(''); + } else if (fillStyle instanceof CanvasPattern_) { + if (width && height) { + var deltaLeft = -min.x; + var deltaTop = -min.y; + lineStr.push(''); + } + } else { + var a = processStyle(ctx.fillStyle); + var color = a.color; + var opacity = a.alpha * ctx.globalAlpha; + lineStr.push(''); + } + } + + contextPrototype.fill = function() { + this.stroke(true); + }; + + contextPrototype.closePath = function() { + this.currentPath_.push({type: 'close'}); + }; + + /** + * @private + */ + contextPrototype.getCoords_ = function(aX, aY) { + var m = this.m_; + return { + x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2, + y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2 + }; + }; + + contextPrototype.save = function() { + var o = {}; + copyState(this, o); + this.aStack_.push(o); + this.mStack_.push(this.m_); + this.m_ = matrixMultiply(createMatrixIdentity(), this.m_); + }; + + contextPrototype.restore = function() { + if (this.aStack_.length) { + copyState(this.aStack_.pop(), this); + this.m_ = this.mStack_.pop(); + } + }; + + function matrixIsFinite(m) { + return isFinite(m[0][0]) && isFinite(m[0][1]) && + isFinite(m[1][0]) && isFinite(m[1][1]) && + isFinite(m[2][0]) && isFinite(m[2][1]); + } + + function setM(ctx, m, updateLineScale) { + if (!matrixIsFinite(m)) { + return; + } + ctx.m_ = m; + + if (updateLineScale) { + // Get the line scale. + // Determinant of this.m_ means how much the area is enlarged by the + // transformation. So its square root can be used as a scale factor + // for width. + var det = m[0][0] * m[1][1] - m[0][1] * m[1][0]; + ctx.lineScale_ = sqrt(abs(det)); + } + } + + contextPrototype.translate = function(aX, aY) { + var m1 = [ + [1, 0, 0], + [0, 1, 0], + [aX, aY, 1] + ]; + + setM(this, matrixMultiply(m1, this.m_), false); + }; + + contextPrototype.rotate = function(aRot) { + var c = mc(aRot); + var s = ms(aRot); + + var m1 = [ + [c, s, 0], + [-s, c, 0], + [0, 0, 1] + ]; + + setM(this, matrixMultiply(m1, this.m_), false); + }; + + contextPrototype.scale = function(aX, aY) { + this.arcScaleX_ *= aX; + this.arcScaleY_ *= aY; + var m1 = [ + [aX, 0, 0], + [0, aY, 0], + [0, 0, 1] + ]; + + setM(this, matrixMultiply(m1, this.m_), true); + }; + + contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) { + var m1 = [ + [m11, m12, 0], + [m21, m22, 0], + [dx, dy, 1] + ]; + + setM(this, matrixMultiply(m1, this.m_), true); + }; + + contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) { + var m = [ + [m11, m12, 0], + [m21, m22, 0], + [dx, dy, 1] + ]; + + setM(this, m, true); + }; + + /** + * The text drawing function. + * The maxWidth argument isn't taken in account, since no browser supports + * it yet. + */ + contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) { + var m = this.m_, + delta = 1000, + left = 0, + right = delta, + offset = {x: 0, y: 0}, + lineStr = []; + + var fontStyle = getComputedStyle(processFontStyle(this.font), + this.element_); + + var fontStyleString = buildStyle(fontStyle); + + var elementStyle = this.element_.currentStyle; + var textAlign = this.textAlign.toLowerCase(); + switch (textAlign) { + case 'left': + case 'center': + case 'right': + break; + case 'end': + textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left'; + break; + case 'start': + textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left'; + break; + default: + textAlign = 'left'; + } + + // 1.75 is an arbitrary number, as there is no info about the text baseline + switch (this.textBaseline) { + case 'hanging': + case 'top': + offset.y = fontStyle.size / 1.75; + break; + case 'middle': + break; + default: + case null: + case 'alphabetic': + case 'ideographic': + case 'bottom': + offset.y = -fontStyle.size / 2.25; + break; + } + + switch(textAlign) { + case 'right': + left = delta; + right = 0.05; + break; + case 'center': + left = right = delta / 2; + break; + } + + var d = this.getCoords_(x + offset.x, y + offset.y); + + lineStr.push(''); + + if (stroke) { + appendStroke(this, lineStr); + } else { + // TODO: Fix the min and max params. + appendFill(this, lineStr, {x: -left, y: 0}, + {x: right, y: fontStyle.size}); + } + + var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' + + m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0'; + + var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z); + + lineStr.push('', + '', + ''); + + this.element_.insertAdjacentHTML('beforeEnd', lineStr.join('')); + }; + + contextPrototype.fillText = function(text, x, y, maxWidth) { + this.drawText_(text, x, y, maxWidth, false); + }; + + contextPrototype.strokeText = function(text, x, y, maxWidth) { + this.drawText_(text, x, y, maxWidth, true); + }; + + contextPrototype.measureText = function(text) { + if (!this.textMeasureEl_) { + var s = ''; + this.element_.insertAdjacentHTML('beforeEnd', s); + this.textMeasureEl_ = this.element_.lastChild; + } + var doc = this.element_.ownerDocument; + this.textMeasureEl_.innerHTML = ''; + this.textMeasureEl_.style.font = this.font; + // Don't use innerHTML or innerText because they allow markup/whitespace. + this.textMeasureEl_.appendChild(doc.createTextNode(text)); + return {width: this.textMeasureEl_.offsetWidth}; + }; + + /******** STUBS ********/ + contextPrototype.clip = function() { + // TODO: Implement + }; + + contextPrototype.arcTo = function() { + // TODO: Implement + }; + + contextPrototype.createPattern = function(image, repetition) { + return new CanvasPattern_(image, repetition); + }; + + // Gradient / Pattern Stubs + function CanvasGradient_(aType) { + this.type_ = aType; + this.x0_ = 0; + this.y0_ = 0; + this.r0_ = 0; + this.x1_ = 0; + this.y1_ = 0; + this.r1_ = 0; + this.colors_ = []; + } + + CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) { + aColor = processStyle(aColor); + this.colors_.push({offset: aOffset, + color: aColor.color, + alpha: aColor.alpha}); + }; + + function CanvasPattern_(image, repetition) { + assertImageIsValid(image); + switch (repetition) { + case 'repeat': + case null: + case '': + this.repetition_ = 'repeat'; + break + case 'repeat-x': + case 'repeat-y': + case 'no-repeat': + this.repetition_ = repetition; + break; + default: + throwException('SYNTAX_ERR'); + } + + this.src_ = image.src; + this.width_ = image.width; + this.height_ = image.height; + } + + function throwException(s) { + throw new DOMException_(s); + } + + function assertImageIsValid(img) { + if (!img || img.nodeType != 1 || img.tagName != 'IMG') { + throwException('TYPE_MISMATCH_ERR'); + } + if (img.readyState != 'complete') { + throwException('INVALID_STATE_ERR'); + } + } + + function DOMException_(s) { + this.code = this[s]; + this.message = s +': DOM Exception ' + this.code; + } + var p = DOMException_.prototype = new Error; + p.INDEX_SIZE_ERR = 1; + p.DOMSTRING_SIZE_ERR = 2; + p.HIERARCHY_REQUEST_ERR = 3; + p.WRONG_DOCUMENT_ERR = 4; + p.INVALID_CHARACTER_ERR = 5; + p.NO_DATA_ALLOWED_ERR = 6; + p.NO_MODIFICATION_ALLOWED_ERR = 7; + p.NOT_FOUND_ERR = 8; + p.NOT_SUPPORTED_ERR = 9; + p.INUSE_ATTRIBUTE_ERR = 10; + p.INVALID_STATE_ERR = 11; + p.SYNTAX_ERR = 12; + p.INVALID_MODIFICATION_ERR = 13; + p.NAMESPACE_ERR = 14; + p.INVALID_ACCESS_ERR = 15; + p.VALIDATION_ERR = 16; + p.TYPE_MISMATCH_ERR = 17; + + // set up externs + G_vmlCanvasManager = G_vmlCanvasManager_; + CanvasRenderingContext2D = CanvasRenderingContext2D_; + CanvasGradient = CanvasGradient_; + CanvasPattern = CanvasPattern_; + DOMException = DOMException_; +})(); + +} // if diff --git a/cosmic-client/cosmic-ui/lib/flot/jquery.colorhelpers.js b/cosmic-client/cosmic-ui/lib/flot/jquery.colorhelpers.js new file mode 100644 index 0000000000..d3524d786f --- /dev/null +++ b/cosmic-client/cosmic-ui/lib/flot/jquery.colorhelpers.js @@ -0,0 +1,179 @@ +/* Plugin for jQuery for working with colors. + * + * Version 1.1. + * + * Inspiration from jQuery color animation plugin by John Resig. + * + * Released under the MIT license by Ole Laursen, October 2009. + * + * Examples: + * + * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString() + * var c = $.color.extract($("#mydiv"), 'background-color'); + * console.log(c.r, c.g, c.b, c.a); + * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)" + * + * Note that .scale() and .add() return the same modified object + * instead of making a new one. + * + * V. 1.1: Fix error handling so e.g. parsing an empty string does + * produce a color rather than just crashing. + */ + +(function($) { + $.color = {}; + + // construct color object with some convenient chainable helpers + $.color.make = function (r, g, b, a) { + var o = {}; + o.r = r || 0; + o.g = g || 0; + o.b = b || 0; + o.a = a != null ? a : 1; + + o.add = function (c, d) { + for (var i = 0; i < c.length; ++i) + o[c.charAt(i)] += d; + return o.normalize(); + }; + + o.scale = function (c, f) { + for (var i = 0; i < c.length; ++i) + o[c.charAt(i)] *= f; + return o.normalize(); + }; + + o.toString = function () { + if (o.a >= 1.0) { + return "rgb("+[o.r, o.g, o.b].join(",")+")"; + } else { + return "rgba("+[o.r, o.g, o.b, o.a].join(",")+")"; + } + }; + + o.normalize = function () { + function clamp(min, value, max) { + return value < min ? min: (value > max ? max: value); + } + + o.r = clamp(0, parseInt(o.r), 255); + o.g = clamp(0, parseInt(o.g), 255); + o.b = clamp(0, parseInt(o.b), 255); + o.a = clamp(0, o.a, 1); + return o; + }; + + o.clone = function () { + return $.color.make(o.r, o.b, o.g, o.a); + }; + + return o.normalize(); + } + + // extract CSS color property from element, going up in the DOM + // if it's "transparent" + $.color.extract = function (elem, css) { + var c; + do { + c = elem.css(css).toLowerCase(); + // keep going until we find an element that has color, or + // we hit the body + if (c != '' && c != 'transparent') + break; + elem = elem.parent(); + } while (!$.nodeName(elem.get(0), "body")); + + // catch Safari's way of signalling transparent + if (c == "rgba(0, 0, 0, 0)") + c = "transparent"; + + return $.color.parse(c); + } + + // parse CSS color string (like "rgb(10, 32, 43)" or "#fff"), + // returns color object, if parsing failed, you get black (0, 0, + // 0) out + $.color.parse = function (str) { + var res, m = $.color.make; + + // Look for rgb(num,num,num) + if (res = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str)) + return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10)); + + // Look for rgba(num,num,num,num) + if (res = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str)) + return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10), parseFloat(res[4])); + + // Look for rgb(num%,num%,num%) + if (res = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str)) + return m(parseFloat(res[1])*2.55, parseFloat(res[2])*2.55, parseFloat(res[3])*2.55); + + // Look for rgba(num%,num%,num%,num) + if (res = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str)) + return m(parseFloat(res[1])*2.55, parseFloat(res[2])*2.55, parseFloat(res[3])*2.55, parseFloat(res[4])); + + // Look for #a0b1c2 + if (res = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str)) + return m(parseInt(res[1], 16), parseInt(res[2], 16), parseInt(res[3], 16)); + + // Look for #fff + if (res = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str)) + return m(parseInt(res[1]+res[1], 16), parseInt(res[2]+res[2], 16), parseInt(res[3]+res[3], 16)); + + // Otherwise, we're most likely dealing with a named color + var name = $.trim(str).toLowerCase(); + if (name == "transparent") + return m(255, 255, 255, 0); + else { + // default to black + res = lookupColors[name] || [0, 0, 0]; + return m(res[0], res[1], res[2]); + } + } + + var lookupColors = { + aqua:[0,255,255], + azure:[240,255,255], + beige:[245,245,220], + black:[0,0,0], + blue:[0,0,255], + brown:[165,42,42], + cyan:[0,255,255], + darkblue:[0,0,139], + darkcyan:[0,139,139], + darkgrey:[169,169,169], + darkgreen:[0,100,0], + darkkhaki:[189,183,107], + darkmagenta:[139,0,139], + darkolivegreen:[85,107,47], + darkorange:[255,140,0], + darkorchid:[153,50,204], + darkred:[139,0,0], + darksalmon:[233,150,122], + darkviolet:[148,0,211], + fuchsia:[255,0,255], + gold:[255,215,0], + green:[0,128,0], + indigo:[75,0,130], + khaki:[240,230,140], + lightblue:[173,216,230], + lightcyan:[224,255,255], + lightgreen:[144,238,144], + lightgrey:[211,211,211], + lightpink:[255,182,193], + lightyellow:[255,255,224], + lime:[0,255,0], + magenta:[255,0,255], + maroon:[128,0,0], + navy:[0,0,128], + olive:[128,128,0], + orange:[255,165,0], + pink:[255,192,203], + purple:[128,0,128], + violet:[128,0,128], + red:[255,0,0], + silver:[192,192,192], + white:[255,255,255], + yellow:[255,255,0] + }; +})(jQuery); diff --git a/cosmic-client/cosmic-ui/lib/flot/jquery.flot.crosshair.js b/cosmic-client/cosmic-ui/lib/flot/jquery.flot.crosshair.js new file mode 100644 index 0000000000..1d433f0074 --- /dev/null +++ b/cosmic-client/cosmic-ui/lib/flot/jquery.flot.crosshair.js @@ -0,0 +1,167 @@ +/* +Flot plugin for showing crosshairs, thin lines, when the mouse hovers +over the plot. + + crosshair: { + mode: null or "x" or "y" or "xy" + color: color + lineWidth: number + } + +Set the mode to one of "x", "y" or "xy". The "x" mode enables a +vertical crosshair that lets you trace the values on the x axis, "y" +enables a horizontal crosshair and "xy" enables them both. "color" is +the color of the crosshair (default is "rgba(170, 0, 0, 0.80)"), +"lineWidth" is the width of the drawn lines (default is 1). + +The plugin also adds four public methods: + + - setCrosshair(pos) + + Set the position of the crosshair. Note that this is cleared if + the user moves the mouse. "pos" is in coordinates of the plot and + should be on the form { x: xpos, y: ypos } (you can use x2/x3/... + if you're using multiple axes), which is coincidentally the same + format as what you get from a "plothover" event. If "pos" is null, + the crosshair is cleared. + + - clearCrosshair() + + Clear the crosshair. + + - lockCrosshair(pos) + + Cause the crosshair to lock to the current location, no longer + updating if the user moves the mouse. Optionally supply a position + (passed on to setCrosshair()) to move it to. + + Example usage: + var myFlot = $.plot( $("#graph"), ..., { crosshair: { mode: "x" } } }; + $("#graph").bind("plothover", function (evt, position, item) { + if (item) { + // Lock the crosshair to the data point being hovered + myFlot.lockCrosshair({ x: item.datapoint[0], y: item.datapoint[1] }); + } + else { + // Return normal crosshair operation + myFlot.unlockCrosshair(); + } + }); + + - unlockCrosshair() + + Free the crosshair to move again after locking it. +*/ + +(function ($) { + var options = { + crosshair: { + mode: null, // one of null, "x", "y" or "xy", + color: "rgba(170, 0, 0, 0.80)", + lineWidth: 1 + } + }; + + function init(plot) { + // position of crosshair in pixels + var crosshair = { x: -1, y: -1, locked: false }; + + plot.setCrosshair = function setCrosshair(pos) { + if (!pos) + crosshair.x = -1; + else { + var o = plot.p2c(pos); + crosshair.x = Math.max(0, Math.min(o.left, plot.width())); + crosshair.y = Math.max(0, Math.min(o.top, plot.height())); + } + + plot.triggerRedrawOverlay(); + }; + + plot.clearCrosshair = plot.setCrosshair; // passes null for pos + + plot.lockCrosshair = function lockCrosshair(pos) { + if (pos) + plot.setCrosshair(pos); + crosshair.locked = true; + } + + plot.unlockCrosshair = function unlockCrosshair() { + crosshair.locked = false; + } + + function onMouseOut(e) { + if (crosshair.locked) + return; + + if (crosshair.x != -1) { + crosshair.x = -1; + plot.triggerRedrawOverlay(); + } + } + + function onMouseMove(e) { + if (crosshair.locked) + return; + + if (plot.getSelection && plot.getSelection()) { + crosshair.x = -1; // hide the crosshair while selecting + return; + } + + var offset = plot.offset(); + crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width())); + crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height())); + plot.triggerRedrawOverlay(); + } + + plot.hooks.bindEvents.push(function (plot, eventHolder) { + if (!plot.getOptions().crosshair.mode) + return; + + eventHolder.mouseout(onMouseOut); + eventHolder.mousemove(onMouseMove); + }); + + plot.hooks.drawOverlay.push(function (plot, ctx) { + var c = plot.getOptions().crosshair; + if (!c.mode) + return; + + var plotOffset = plot.getPlotOffset(); + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + if (crosshair.x != -1) { + ctx.strokeStyle = c.color; + ctx.lineWidth = c.lineWidth; + ctx.lineJoin = "round"; + + ctx.beginPath(); + if (c.mode.indexOf("x") != -1) { + ctx.moveTo(crosshair.x, 0); + ctx.lineTo(crosshair.x, plot.height()); + } + if (c.mode.indexOf("y") != -1) { + ctx.moveTo(0, crosshair.y); + ctx.lineTo(plot.width(), crosshair.y); + } + ctx.stroke(); + } + ctx.restore(); + }); + + plot.hooks.shutdown.push(function (plot, eventHolder) { + eventHolder.unbind("mouseout", onMouseOut); + eventHolder.unbind("mousemove", onMouseMove); + }); + } + + $.plot.plugins.push({ + init: init, + options: options, + name: 'crosshair', + version: '1.0' + }); +})(jQuery); diff --git a/cosmic-client/cosmic-ui/lib/flot/jquery.flot.fillbetween.js b/cosmic-client/cosmic-ui/lib/flot/jquery.flot.fillbetween.js new file mode 100644 index 0000000000..69700e79ce --- /dev/null +++ b/cosmic-client/cosmic-ui/lib/flot/jquery.flot.fillbetween.js @@ -0,0 +1,183 @@ +/* +Flot plugin for computing bottoms for filled line and bar charts. + +The case: you've got two series that you want to fill the area +between. In Flot terms, you need to use one as the fill bottom of the +other. You can specify the bottom of each data point as the third +coordinate manually, or you can use this plugin to compute it for you. + +In order to name the other series, you need to give it an id, like this + + var dataset = [ + { data: [ ... ], id: "foo" } , // use default bottom + { data: [ ... ], fillBetween: "foo" }, // use first dataset as bottom + ]; + + $.plot($("#placeholder"), dataset, { line: { show: true, fill: true }}); + +As a convenience, if the id given is a number that doesn't appear as +an id in the series, it is interpreted as the index in the array +instead (so fillBetween: 0 can also mean the first series). + +Internally, the plugin modifies the datapoints in each series. For +line series, extra data points might be inserted through +interpolation. Note that at points where the bottom line is not +defined (due to a null point or start/end of line), the current line +will show a gap too. The algorithm comes from the jquery.flot.stack.js +plugin, possibly some code could be shared. +*/ + +(function ($) { + var options = { + series: { fillBetween: null } // or number + }; + + function init(plot) { + function findBottomSeries(s, allseries) { + var i; + for (i = 0; i < allseries.length; ++i) { + if (allseries[i].id == s.fillBetween) + return allseries[i]; + } + + if (typeof s.fillBetween == "number") { + i = s.fillBetween; + + if (i < 0 || i >= allseries.length) + return null; + + return allseries[i]; + } + + return null; + } + + function computeFillBottoms(plot, s, datapoints) { + if (s.fillBetween == null) + return; + + var other = findBottomSeries(s, plot.getData()); + if (!other) + return; + + var ps = datapoints.pointsize, + points = datapoints.points, + otherps = other.datapoints.pointsize, + otherpoints = other.datapoints.points, + newpoints = [], + px, py, intery, qx, qy, bottom, + withlines = s.lines.show, + withbottom = ps > 2 && datapoints.format[2].y, + withsteps = withlines && s.lines.steps, + fromgap = true, + i = 0, j = 0, l; + + while (true) { + if (i >= points.length) + break; + + l = newpoints.length; + + if (points[i] == null) { + // copy gaps + for (m = 0; m < ps; ++m) + newpoints.push(points[i + m]); + i += ps; + } + else if (j >= otherpoints.length) { + // for lines, we can't use the rest of the points + if (!withlines) { + for (m = 0; m < ps; ++m) + newpoints.push(points[i + m]); + } + i += ps; + } + else if (otherpoints[j] == null) { + // oops, got a gap + for (m = 0; m < ps; ++m) + newpoints.push(null); + fromgap = true; + j += otherps; + } + else { + // cases where we actually got two points + px = points[i]; + py = points[i + 1]; + qx = otherpoints[j]; + qy = otherpoints[j + 1]; + bottom = 0; + + if (px == qx) { + for (m = 0; m < ps; ++m) + newpoints.push(points[i + m]); + + //newpoints[l + 1] += qy; + bottom = qy; + + i += ps; + j += otherps; + } + else if (px > qx) { + // we got past point below, might need to + // insert interpolated extra point + if (withlines && i > 0 && points[i - ps] != null) { + intery = py + (points[i - ps + 1] - py) * (qx - px) / (points[i - ps] - px); + newpoints.push(qx); + newpoints.push(intery) + for (m = 2; m < ps; ++m) + newpoints.push(points[i + m]); + bottom = qy; + } + + j += otherps; + } + else { // px < qx + if (fromgap && withlines) { + // if we come from a gap, we just skip this point + i += ps; + continue; + } + + for (m = 0; m < ps; ++m) + newpoints.push(points[i + m]); + + // we might be able to interpolate a point below, + // this can give us a better y + if (withlines && j > 0 && otherpoints[j - otherps] != null) + bottom = qy + (otherpoints[j - otherps + 1] - qy) * (px - qx) / (otherpoints[j - otherps] - qx); + + //newpoints[l + 1] += bottom; + + i += ps; + } + + fromgap = false; + + if (l != newpoints.length && withbottom) + newpoints[l + 2] = bottom; + } + + // maintain the line steps invariant + if (withsteps && l != newpoints.length && l > 0 + && newpoints[l] != null + && newpoints[l] != newpoints[l - ps] + && newpoints[l + 1] != newpoints[l - ps + 1]) { + for (m = 0; m < ps; ++m) + newpoints[l + ps + m] = newpoints[l + m]; + newpoints[l + 1] = newpoints[l - ps + 1]; + } + } + + datapoints.points = newpoints; + } + + plot.hooks.processDatapoints.push(computeFillBottoms); + } + + $.plot.plugins.push({ + init: init, + options: options, + name: 'fillbetween', + version: '1.0' + }); +})(jQuery); diff --git a/cosmic-client/cosmic-ui/lib/flot/jquery.flot.image.js b/cosmic-client/cosmic-ui/lib/flot/jquery.flot.image.js new file mode 100644 index 0000000000..29ccb125f4 --- /dev/null +++ b/cosmic-client/cosmic-ui/lib/flot/jquery.flot.image.js @@ -0,0 +1,238 @@ +/* +Flot plugin for plotting images, e.g. useful for putting ticks on a +prerendered complex visualization. + +The data syntax is [[image, x1, y1, x2, y2], ...] where (x1, y1) and +(x2, y2) are where you intend the two opposite corners of the image to +end up in the plot. Image must be a fully loaded Javascript image (you +can make one with new Image()). If the image is not complete, it's +skipped when plotting. + +There are two helpers included for retrieving images. The easiest work +the way that you put in URLs instead of images in the data (like +["myimage.png", 0, 0, 10, 10]), then call $.plot.image.loadData(data, +options, callback) where data and options are the same as you pass in +to $.plot. This loads the images, replaces the URLs in the data with +the corresponding images and calls "callback" when all images are +loaded (or failed loading). In the callback, you can then call $.plot +with the data set. See the included example. + +A more low-level helper, $.plot.image.load(urls, callback) is also +included. Given a list of URLs, it calls callback with an object +mapping from URL to Image object when all images are loaded or have +failed loading. + +Options for the plugin are + + series: { + images: { + show: boolean + anchor: "corner" or "center" + alpha: [0,1] + } + } + +which can be specified for a specific series + + $.plot($("#placeholder"), [{ data: [ ... ], images: { ... } ]) + +Note that because the data format is different from usual data points, +you can't use images with anything else in a specific data series. + +Setting "anchor" to "center" causes the pixels in the image to be +anchored at the corner pixel centers inside of at the pixel corners, +effectively letting half a pixel stick out to each side in the plot. + + +A possible future direction could be support for tiling for large +images (like Google Maps). + +*/ + +(function ($) { + var options = { + series: { + images: { + show: false, + alpha: 1, + anchor: "corner" // or "center" + } + } + }; + + $.plot.image = {}; + + $.plot.image.loadDataImages = function (series, options, callback) { + var urls = [], points = []; + + var defaultShow = options.series.images.show; + + $.each(series, function (i, s) { + if (!(defaultShow || s.images.show)) + return; + + if (s.data) + s = s.data; + + $.each(s, function (i, p) { + if (typeof p[0] == "string") { + urls.push(p[0]); + points.push(p); + } + }); + }); + + $.plot.image.load(urls, function (loadedImages) { + $.each(points, function (i, p) { + var url = p[0]; + if (loadedImages[url]) + p[0] = loadedImages[url]; + }); + + callback(); + }); + } + + $.plot.image.load = function (urls, callback) { + var missing = urls.length, loaded = {}; + if (missing == 0) + callback({}); + + $.each(urls, function (i, url) { + var handler = function () { + --missing; + + loaded[url] = this; + + if (missing == 0) + callback(loaded); + }; + + $('').load(handler).error(handler).attr('src', url); + }); + } + + function drawSeries(plot, ctx, series) { + var plotOffset = plot.getPlotOffset(); + + if (!series.images || !series.images.show) + return; + + var points = series.datapoints.points, + ps = series.datapoints.pointsize; + + for (var i = 0; i < points.length; i += ps) { + var img = points[i], + x1 = points[i + 1], y1 = points[i + 2], + x2 = points[i + 3], y2 = points[i + 4], + xaxis = series.xaxis, yaxis = series.yaxis, + tmp; + + // actually we should check img.complete, but it + // appears to be a somewhat unreliable indicator in + // IE6 (false even after load event) + if (!img || img.width <= 0 || img.height <= 0) + continue; + + if (x1 > x2) { + tmp = x2; + x2 = x1; + x1 = tmp; + } + if (y1 > y2) { + tmp = y2; + y2 = y1; + y1 = tmp; + } + + // if the anchor is at the center of the pixel, expand the + // image by 1/2 pixel in each direction + if (series.images.anchor == "center") { + tmp = 0.5 * (x2-x1) / (img.width - 1); + x1 -= tmp; + x2 += tmp; + tmp = 0.5 * (y2-y1) / (img.height - 1); + y1 -= tmp; + y2 += tmp; + } + + // clip + if (x1 == x2 || y1 == y2 || + x1 >= xaxis.max || x2 <= xaxis.min || + y1 >= yaxis.max || y2 <= yaxis.min) + continue; + + var sx1 = 0, sy1 = 0, sx2 = img.width, sy2 = img.height; + if (x1 < xaxis.min) { + sx1 += (sx2 - sx1) * (xaxis.min - x1) / (x2 - x1); + x1 = xaxis.min; + } + + if (x2 > xaxis.max) { + sx2 += (sx2 - sx1) * (xaxis.max - x2) / (x2 - x1); + x2 = xaxis.max; + } + + if (y1 < yaxis.min) { + sy2 += (sy1 - sy2) * (yaxis.min - y1) / (y2 - y1); + y1 = yaxis.min; + } + + if (y2 > yaxis.max) { + sy1 += (sy1 - sy2) * (yaxis.max - y2) / (y2 - y1); + y2 = yaxis.max; + } + + x1 = xaxis.p2c(x1); + x2 = xaxis.p2c(x2); + y1 = yaxis.p2c(y1); + y2 = yaxis.p2c(y2); + + // the transformation may have swapped us + if (x1 > x2) { + tmp = x2; + x2 = x1; + x1 = tmp; + } + if (y1 > y2) { + tmp = y2; + y2 = y1; + y1 = tmp; + } + + tmp = ctx.globalAlpha; + ctx.globalAlpha *= series.images.alpha; + ctx.drawImage(img, + sx1, sy1, sx2 - sx1, sy2 - sy1, + x1 + plotOffset.left, y1 + plotOffset.top, + x2 - x1, y2 - y1); + ctx.globalAlpha = tmp; + } + } + + function processRawData(plot, series, data, datapoints) { + if (!series.images.show) + return; + + // format is Image, x1, y1, x2, y2 (opposite corners) + datapoints.format = [ + { required: true }, + { x: true, number: true, required: true }, + { y: true, number: true, required: true }, + { x: true, number: true, required: true }, + { y: true, number: true, required: true } + ]; + } + + function init(plot) { + plot.hooks.processRawData.push(processRawData); + plot.hooks.drawSeries.push(drawSeries); + } + + $.plot.plugins.push({ + init: init, + options: options, + name: 'image', + version: '1.1' + }); +})(jQuery); diff --git a/cosmic-client/cosmic-ui/lib/flot/jquery.flot.js b/cosmic-client/cosmic-ui/lib/flot/jquery.flot.js new file mode 100644 index 0000000000..aabc544e9a --- /dev/null +++ b/cosmic-client/cosmic-ui/lib/flot/jquery.flot.js @@ -0,0 +1,2599 @@ +/*! Javascript plotting library for jQuery, v. 0.7. + * + * Released under the MIT license by IOLA, December 2007. + * + */ + +// first an inline dependency, jquery.colorhelpers.js, we inline it here +// for convenience + +/* Plugin for jQuery for working with colors. + * + * Version 1.1. + * + * Inspiration from jQuery color animation plugin by John Resig. + * + * Released under the MIT license by Ole Laursen, October 2009. + * + * Examples: + * + * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString() + * var c = $.color.extract($("#mydiv"), 'background-color'); + * console.log(c.r, c.g, c.b, c.a); + * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)" + * + * Note that .scale() and .add() return the same modified object + * instead of making a new one. + * + * V. 1.1: Fix error handling so e.g. parsing an empty string does + * produce a color rather than just crashing. + */ +(function(B){B.color={};B.color.make=function(F,E,C,D){var G={};G.r=F||0;G.g=E||0;G.b=C||0;G.a=D!=null?D:1;G.add=function(J,I){for(var H=0;H=1){return"rgb("+[G.r,G.g,G.b].join(",")+")"}else{return"rgba("+[G.r,G.g,G.b,G.a].join(",")+")"}};G.normalize=function(){function H(J,K,I){return KI?I:K)}G.r=H(0,parseInt(G.r),255);G.g=H(0,parseInt(G.g),255);G.b=H(0,parseInt(G.b),255);G.a=H(0,G.a,1);return G};G.clone=function(){return B.color.make(G.r,G.b,G.g,G.a)};return G.normalize()};B.color.extract=function(D,C){var E;do{E=D.css(C).toLowerCase();if(E!=""&&E!="transparent"){break}D=D.parent()}while(!B.nodeName(D.get(0),"body"));if(E=="rgba(0, 0, 0, 0)"){E="transparent"}return B.color.parse(E)};B.color.parse=function(F){var E,C=B.color.make;if(E=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10))}if(E=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10),parseFloat(E[4]))}if(E=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55)}if(E=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55,parseFloat(E[4]))}if(E=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(F)){return C(parseInt(E[1],16),parseInt(E[2],16),parseInt(E[3],16))}if(E=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(F)){return C(parseInt(E[1]+E[1],16),parseInt(E[2]+E[2],16),parseInt(E[3]+E[3],16))}var D=B.trim(F).toLowerCase();if(D=="transparent"){return C(255,255,255,0)}else{E=A[D]||[0,0,0];return C(E[0],E[1],E[2])}};var A={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery); + +// the actual Flot code +(function($) { + function Plot(placeholder, data_, options_, plugins) { + // data is on the form: + // [ series1, series2 ... ] + // where series is either just the data as [ [x1, y1], [x2, y2], ... ] + // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... } + + var series = [], + options = { + // the color theme used for graphs + colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"], + legend: { + show: true, + noColumns: 1, // number of colums in legend table + labelFormatter: null, // fn: string -> string + labelBoxBorderColor: "#ccc", // border color for the little label boxes + container: null, // container (as jQuery object) to put legend in, null means default on top of graph + position: "ne", // position of default legend container within plot + margin: 5, // distance from grid edge to default legend container within plot + backgroundColor: null, // null means auto-detect + backgroundOpacity: 0.85 // set to 0 to avoid background + }, + xaxis: { + show: null, // null = auto-detect, true = always, false = never + position: "bottom", // or "top" + mode: null, // null or "time" + color: null, // base color, labels, ticks + tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)" + transform: null, // null or f: number -> number to transform axis + inverseTransform: null, // if transform is set, this should be the inverse function + min: null, // min. value to show, null means set automatically + max: null, // max. value to show, null means set automatically + autoscaleMargin: null, // margin in % to add if auto-setting min/max + ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks + tickFormatter: null, // fn: number -> string + labelWidth: null, // size of tick labels in pixels + labelHeight: null, + reserveSpace: null, // whether to reserve space even if axis isn't shown + tickLength: null, // size in pixels of ticks, or "full" for whole line + alignTicksWithAxis: null, // axis number or null for no sync + + // mode specific options + tickDecimals: null, // no. of decimals, null means auto + tickSize: null, // number or [number, "unit"] + minTickSize: null, // number or [number, "unit"] + monthNames: null, // list of names of months + timeformat: null, // format string to use + twelveHourClock: false // 12 or 24 time in time mode + }, + yaxis: { + autoscaleMargin: 0.02, + position: "left" // or "right" + }, + xaxes: [], + yaxes: [], + series: { + points: { + show: false, + radius: 3, + lineWidth: 2, // in pixels + fill: true, + fillColor: "#ffffff", + symbol: "circle" // or callback + }, + lines: { + // we don't put in show: false so we can see + // whether lines were actively disabled + lineWidth: 2, // in pixels + fill: false, + fillColor: null, + steps: false + }, + bars: { + show: false, + lineWidth: 2, // in pixels + barWidth: 1, // in units of the x axis + fill: true, + fillColor: null, + align: "left", // or "center" + horizontal: false + }, + shadowSize: 3 + }, + grid: { + show: true, + aboveData: false, + color: "#545454", // primary color used for outline and labels + backgroundColor: null, // null for transparent, else color + borderColor: null, // set if different from the grid color + tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)" + labelMargin: 5, // in pixels + axisMargin: 8, // in pixels + borderWidth: 2, // in pixels + minBorderMargin: null, // in pixels, null means taken from points radius + markings: null, // array of ranges or fn: axes -> array of ranges + markingsColor: "#f4f4f4", + markingsLineWidth: 2, + // interactive stuff + clickable: false, + hoverable: false, + autoHighlight: true, // highlight in case mouse is near + mouseActiveRadius: 10 // how far the mouse can be away to activate an item + }, + hooks: {} + }, + canvas = null, // the canvas for the plot itself + overlay = null, // canvas for interactive stuff on top of plot + eventHolder = null, // jQuery object that events should be bound to + ctx = null, octx = null, + xaxes = [], yaxes = [], + plotOffset = { left: 0, right: 0, top: 0, bottom: 0}, + canvasWidth = 0, canvasHeight = 0, + plotWidth = 0, plotHeight = 0, + hooks = { + processOptions: [], + processRawData: [], + processDatapoints: [], + drawSeries: [], + draw: [], + bindEvents: [], + drawOverlay: [], + shutdown: [] + }, + plot = this; + + // public functions + plot.setData = setData; + plot.setupGrid = setupGrid; + plot.draw = draw; + plot.getPlaceholder = function() { return placeholder; }; + plot.getCanvas = function() { return canvas; }; + plot.getPlotOffset = function() { return plotOffset; }; + plot.width = function () { return plotWidth; }; + plot.height = function () { return plotHeight; }; + plot.offset = function () { + var o = eventHolder.offset(); + o.left += plotOffset.left; + o.top += plotOffset.top; + return o; + }; + plot.getData = function () { return series; }; + plot.getAxes = function () { + var res = {}, i; + $.each(xaxes.concat(yaxes), function (_, axis) { + if (axis) + res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis; + }); + return res; + }; + plot.getXAxes = function () { return xaxes; }; + plot.getYAxes = function () { return yaxes; }; + plot.c2p = canvasToAxisCoords; + plot.p2c = axisToCanvasCoords; + plot.getOptions = function () { return options; }; + plot.highlight = highlight; + plot.unhighlight = unhighlight; + plot.triggerRedrawOverlay = triggerRedrawOverlay; + plot.pointOffset = function(point) { + return { + left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left), + top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top) + }; + }; + plot.shutdown = shutdown; + plot.resize = function () { + getCanvasDimensions(); + resizeCanvas(canvas); + resizeCanvas(overlay); + }; + + // public attributes + plot.hooks = hooks; + + // initialize + initPlugins(plot); + parseOptions(options_); + setupCanvases(); + setData(data_); + setupGrid(); + draw(); + bindEvents(); + + + function executeHooks(hook, args) { + args = [plot].concat(args); + for (var i = 0; i < hook.length; ++i) + hook[i].apply(this, args); + } + + function initPlugins() { + for (var i = 0; i < plugins.length; ++i) { + var p = plugins[i]; + p.init(plot); + if (p.options) + $.extend(true, options, p.options); + } + } + + function parseOptions(opts) { + var i; + + $.extend(true, options, opts); + + if (options.xaxis.color == null) + options.xaxis.color = options.grid.color; + if (options.yaxis.color == null) + options.yaxis.color = options.grid.color; + + if (options.xaxis.tickColor == null) // backwards-compatibility + options.xaxis.tickColor = options.grid.tickColor; + if (options.yaxis.tickColor == null) // backwards-compatibility + options.yaxis.tickColor = options.grid.tickColor; + + if (options.grid.borderColor == null) + options.grid.borderColor = options.grid.color; + if (options.grid.tickColor == null) + options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString(); + + // fill in defaults in axes, copy at least always the + // first as the rest of the code assumes it'll be there + for (i = 0; i < Math.max(1, options.xaxes.length); ++i) + options.xaxes[i] = $.extend(true, {}, options.xaxis, options.xaxes[i]); + for (i = 0; i < Math.max(1, options.yaxes.length); ++i) + options.yaxes[i] = $.extend(true, {}, options.yaxis, options.yaxes[i]); + + // backwards compatibility, to be removed in future + if (options.xaxis.noTicks && options.xaxis.ticks == null) + options.xaxis.ticks = options.xaxis.noTicks; + if (options.yaxis.noTicks && options.yaxis.ticks == null) + options.yaxis.ticks = options.yaxis.noTicks; + if (options.x2axis) { + options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis); + options.xaxes[1].position = "top"; + } + if (options.y2axis) { + options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis); + options.yaxes[1].position = "right"; + } + if (options.grid.coloredAreas) + options.grid.markings = options.grid.coloredAreas; + if (options.grid.coloredAreasColor) + options.grid.markingsColor = options.grid.coloredAreasColor; + if (options.lines) + $.extend(true, options.series.lines, options.lines); + if (options.points) + $.extend(true, options.series.points, options.points); + if (options.bars) + $.extend(true, options.series.bars, options.bars); + if (options.shadowSize != null) + options.series.shadowSize = options.shadowSize; + + // save options on axes for future reference + for (i = 0; i < options.xaxes.length; ++i) + getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i]; + for (i = 0; i < options.yaxes.length; ++i) + getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i]; + + // add hooks from options + for (var n in hooks) + if (options.hooks[n] && options.hooks[n].length) + hooks[n] = hooks[n].concat(options.hooks[n]); + + executeHooks(hooks.processOptions, [options]); + } + + function setData(d) { + series = parseData(d); + fillInSeriesOptions(); + processData(); + } + + function parseData(d) { + var res = []; + for (var i = 0; i < d.length; ++i) { + var s = $.extend(true, {}, options.series); + + if (d[i].data != null) { + s.data = d[i].data; // move the data instead of deep-copy + delete d[i].data; + + $.extend(true, s, d[i]); + + d[i].data = s.data; + } + else + s.data = d[i]; + res.push(s); + } + + return res; + } + + function axisNumber(obj, coord) { + var a = obj[coord + "axis"]; + if (typeof a == "object") // if we got a real axis, extract number + a = a.n; + if (typeof a != "number") + a = 1; // default to first axis + return a; + } + + function allAxes() { + // return flat array without annoying null entries + return $.grep(xaxes.concat(yaxes), function (a) { return a; }); + } + + function canvasToAxisCoords(pos) { + // return an object with x/y corresponding to all used axes + var res = {}, i, axis; + for (i = 0; i < xaxes.length; ++i) { + axis = xaxes[i]; + if (axis && axis.used) + res["x" + axis.n] = axis.c2p(pos.left); + } + + for (i = 0; i < yaxes.length; ++i) { + axis = yaxes[i]; + if (axis && axis.used) + res["y" + axis.n] = axis.c2p(pos.top); + } + + if (res.x1 !== undefined) + res.x = res.x1; + if (res.y1 !== undefined) + res.y = res.y1; + + return res; + } + + function axisToCanvasCoords(pos) { + // get canvas coords from the first pair of x/y found in pos + var res = {}, i, axis, key; + + for (i = 0; i < xaxes.length; ++i) { + axis = xaxes[i]; + if (axis && axis.used) { + key = "x" + axis.n; + if (pos[key] == null && axis.n == 1) + key = "x"; + + if (pos[key] != null) { + res.left = axis.p2c(pos[key]); + break; + } + } + } + + for (i = 0; i < yaxes.length; ++i) { + axis = yaxes[i]; + if (axis && axis.used) { + key = "y" + axis.n; + if (pos[key] == null && axis.n == 1) + key = "y"; + + if (pos[key] != null) { + res.top = axis.p2c(pos[key]); + break; + } + } + } + + return res; + } + + function getOrCreateAxis(axes, number) { + if (!axes[number - 1]) + axes[number - 1] = { + n: number, // save the number for future reference + direction: axes == xaxes ? "x" : "y", + options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis) + }; + + return axes[number - 1]; + } + + function fillInSeriesOptions() { + var i; + + // collect what we already got of colors + var neededColors = series.length, + usedColors = [], + assignedColors = []; + for (i = 0; i < series.length; ++i) { + var sc = series[i].color; + if (sc != null) { + --neededColors; + if (typeof sc == "number") + assignedColors.push(sc); + else + usedColors.push($.color.parse(series[i].color)); + } + } + + // we might need to generate more colors if higher indices + // are assigned + for (i = 0; i < assignedColors.length; ++i) { + neededColors = Math.max(neededColors, assignedColors[i] + 1); + } + + // produce colors as needed + var colors = [], variation = 0; + i = 0; + while (colors.length < neededColors) { + var c; + if (options.colors.length == i) // check degenerate case + c = $.color.make(100, 100, 100); + else + c = $.color.parse(options.colors[i]); + + // vary color if needed + var sign = variation % 2 == 1 ? -1 : 1; + c.scale('rgb', 1 + sign * Math.ceil(variation / 2) * 0.2) + + // FIXME: if we're getting to close to something else, + // we should probably skip this one + colors.push(c); + + ++i; + if (i >= options.colors.length) { + i = 0; + ++variation; + } + } + + // fill in the options + var colori = 0, s; + for (i = 0; i < series.length; ++i) { + s = series[i]; + + // assign colors + if (s.color == null) { + s.color = colors[colori].toString(); + ++colori; + } + else if (typeof s.color == "number") + s.color = colors[s.color].toString(); + + // turn on lines automatically in case nothing is set + if (s.lines.show == null) { + var v, show = true; + for (v in s) + if (s[v] && s[v].show) { + show = false; + break; + } + if (show) + s.lines.show = true; + } + + // setup axes + s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x")); + s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y")); + } + } + + function processData() { + var topSentry = Number.POSITIVE_INFINITY, + bottomSentry = Number.NEGATIVE_INFINITY, + fakeInfinity = Number.MAX_VALUE, + i, j, k, m, length, + s, points, ps, x, y, axis, val, f, p; + + function updateAxis(axis, min, max) { + if (min < axis.datamin && min != -fakeInfinity) + axis.datamin = min; + if (max > axis.datamax && max != fakeInfinity) + axis.datamax = max; + } + + $.each(allAxes(), function (_, axis) { + // init axis + axis.datamin = topSentry; + axis.datamax = bottomSentry; + axis.used = false; + }); + + for (i = 0; i < series.length; ++i) { + s = series[i]; + s.datapoints = { points: [] }; + + executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]); + } + + // first pass: clean and copy data + for (i = 0; i < series.length; ++i) { + s = series[i]; + + var data = s.data, format = s.datapoints.format; + + if (!format) { + format = []; + // find out how to copy + format.push({ x: true, number: true, required: true }); + format.push({ y: true, number: true, required: true }); + + if (s.bars.show || (s.lines.show && s.lines.fill)) { + format.push({ y: true, number: true, required: false, defaultValue: 0 }); + if (s.bars.horizontal) { + delete format[format.length - 1].y; + format[format.length - 1].x = true; + } + } + + s.datapoints.format = format; + } + + if (s.datapoints.pointsize != null) + continue; // already filled in + + s.datapoints.pointsize = format.length; + + ps = s.datapoints.pointsize; + points = s.datapoints.points; + + insertSteps = s.lines.show && s.lines.steps; + s.xaxis.used = s.yaxis.used = true; + + for (j = k = 0; j < data.length; ++j, k += ps) { + p = data[j]; + + var nullify = p == null; + if (!nullify) { + for (m = 0; m < ps; ++m) { + val = p[m]; + f = format[m]; + + if (f) { + if (f.number && val != null) { + val = +val; // convert to number + if (isNaN(val)) + val = null; + else if (val == Infinity) + val = fakeInfinity; + else if (val == -Infinity) + val = -fakeInfinity; + } + + if (val == null) { + if (f.required) + nullify = true; + + if (f.defaultValue != null) + val = f.defaultValue; + } + } + + points[k + m] = val; + } + } + + if (nullify) { + for (m = 0; m < ps; ++m) { + val = points[k + m]; + if (val != null) { + f = format[m]; + // extract min/max info + if (f.x) + updateAxis(s.xaxis, val, val); + if (f.y) + updateAxis(s.yaxis, val, val); + } + points[k + m] = null; + } + } + else { + // a little bit of line specific stuff that + // perhaps shouldn't be here, but lacking + // better means... + if (insertSteps && k > 0 + && points[k - ps] != null + && points[k - ps] != points[k] + && points[k - ps + 1] != points[k + 1]) { + // copy the point to make room for a middle point + for (m = 0; m < ps; ++m) + points[k + ps + m] = points[k + m]; + + // middle point has same y + points[k + 1] = points[k - ps + 1]; + + // we've added a point, better reflect that + k += ps; + } + } + } + } + + // give the hooks a chance to run + for (i = 0; i < series.length; ++i) { + s = series[i]; + + executeHooks(hooks.processDatapoints, [ s, s.datapoints]); + } + + // second pass: find datamax/datamin for auto-scaling + for (i = 0; i < series.length; ++i) { + s = series[i]; + points = s.datapoints.points, + ps = s.datapoints.pointsize; + + var xmin = topSentry, ymin = topSentry, + xmax = bottomSentry, ymax = bottomSentry; + + for (j = 0; j < points.length; j += ps) { + if (points[j] == null) + continue; + + for (m = 0; m < ps; ++m) { + val = points[j + m]; + f = format[m]; + if (!f || val == fakeInfinity || val == -fakeInfinity) + continue; + + if (f.x) { + if (val < xmin) + xmin = val; + if (val > xmax) + xmax = val; + } + if (f.y) { + if (val < ymin) + ymin = val; + if (val > ymax) + ymax = val; + } + } + } + + if (s.bars.show) { + // make sure we got room for the bar on the dancing floor + var delta = s.bars.align == "left" ? 0 : -s.bars.barWidth/2; + if (s.bars.horizontal) { + ymin += delta; + ymax += delta + s.bars.barWidth; + } + else { + xmin += delta; + xmax += delta + s.bars.barWidth; + } + } + + updateAxis(s.xaxis, xmin, xmax); + updateAxis(s.yaxis, ymin, ymax); + } + + $.each(allAxes(), function (_, axis) { + if (axis.datamin == topSentry) + axis.datamin = null; + if (axis.datamax == bottomSentry) + axis.datamax = null; + }); + } + + function makeCanvas(skipPositioning, cls) { + var c = document.createElement('canvas'); + c.className = cls; + c.width = canvasWidth; + c.height = canvasHeight; + + if (!skipPositioning) + $(c).css({ position: 'absolute', left: 0, top: 0 }); + + $(c).appendTo(placeholder); + + if (!c.getContext) // excanvas hack + c = window.G_vmlCanvasManager.initElement(c); + + // used for resetting in case we get replotted + c.getContext("2d").save(); + + return c; + } + + function getCanvasDimensions() { + canvasWidth = placeholder.width(); + canvasHeight = placeholder.height(); + + if (canvasWidth <= 0 || canvasHeight <= 0) + throw "Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight; + } + + function resizeCanvas(c) { + // resizing should reset the state (excanvas seems to be + // buggy though) + if (c.width != canvasWidth) + c.width = canvasWidth; + + if (c.height != canvasHeight) + c.height = canvasHeight; + + // so try to get back to the initial state (even if it's + // gone now, this should be safe according to the spec) + var cctx = c.getContext("2d"); + cctx.restore(); + + // and save again + cctx.save(); + } + + function setupCanvases() { + var reused, + existingCanvas = placeholder.children("canvas.base"), + existingOverlay = placeholder.children("canvas.overlay"); + + if (existingCanvas.length == 0 || existingOverlay == 0) { + // init everything + + placeholder.html(""); // make sure placeholder is clear + + placeholder.css({ padding: 0 }); // padding messes up the positioning + + if (placeholder.css("position") == 'static') + placeholder.css("position", "relative"); // for positioning labels and overlay + + getCanvasDimensions(); + + canvas = makeCanvas(true, "base"); + overlay = makeCanvas(false, "overlay"); // overlay canvas for interactive features + + reused = false; + } + else { + // reuse existing elements + + canvas = existingCanvas.get(0); + overlay = existingOverlay.get(0); + + reused = true; + } + + ctx = canvas.getContext("2d"); + octx = overlay.getContext("2d"); + + // we include the canvas in the event holder too, because IE 7 + // sometimes has trouble with the stacking order + eventHolder = $([overlay, canvas]); + + if (reused) { + // run shutdown in the old plot object + placeholder.data("plot").shutdown(); + + // reset reused canvases + plot.resize(); + + // make sure overlay pixels are cleared (canvas is cleared when we redraw) + octx.clearRect(0, 0, canvasWidth, canvasHeight); + + // then whack any remaining obvious garbage left + eventHolder.unbind(); + placeholder.children().not([canvas, overlay]).remove(); + } + + // save in case we get replotted + placeholder.data("plot", plot); + } + + function bindEvents() { + // bind events + if (options.grid.hoverable) { + eventHolder.mousemove(onMouseMove); + eventHolder.mouseleave(onMouseLeave); + } + + if (options.grid.clickable) + eventHolder.click(onClick); + + executeHooks(hooks.bindEvents, [eventHolder]); + } + + function shutdown() { + if (redrawTimeout) + clearTimeout(redrawTimeout); + + eventHolder.unbind("mousemove", onMouseMove); + eventHolder.unbind("mouseleave", onMouseLeave); + eventHolder.unbind("click", onClick); + + executeHooks(hooks.shutdown, [eventHolder]); + } + + function setTransformationHelpers(axis) { + // set helper functions on the axis, assumes plot area + // has been computed already + + function identity(x) { return x; } + + var s, m, t = axis.options.transform || identity, + it = axis.options.inverseTransform; + + // precompute how much the axis is scaling a point + // in canvas space + if (axis.direction == "x") { + s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min)); + m = Math.min(t(axis.max), t(axis.min)); + } + else { + s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min)); + s = -s; + m = Math.max(t(axis.max), t(axis.min)); + } + + // data point to canvas coordinate + if (t == identity) // slight optimization + axis.p2c = function (p) { return (p - m) * s; }; + else + axis.p2c = function (p) { return (t(p) - m) * s; }; + // canvas coordinate to data point + if (!it) + axis.c2p = function (c) { return m + c / s; }; + else + axis.c2p = function (c) { return it(m + c / s); }; + } + + function measureTickLabels(axis) { + var opts = axis.options, i, ticks = axis.ticks || [], labels = [], + l, w = opts.labelWidth, h = opts.labelHeight, dummyDiv; + + function makeDummyDiv(labels, width) { + return $('
' + + '
' + + labels.join("") + '
') + .appendTo(placeholder); + } + + if (axis.direction == "x") { + // to avoid measuring the widths of the labels (it's slow), we + // construct fixed-size boxes and put the labels inside + // them, we don't need the exact figures and the + // fixed-size box content is easy to center + if (w == null) + w = Math.floor(canvasWidth / (ticks.length > 0 ? ticks.length : 1)); + + // measure x label heights + if (h == null) { + labels = []; + for (i = 0; i < ticks.length; ++i) { + l = ticks[i].label; + if (l) + labels.push('
' + l + '
'); + } + + if (labels.length > 0) { + // stick them all in the same div and measure + // collective height + labels.push('
'); + dummyDiv = makeDummyDiv(labels, "width:10000px;"); + h = dummyDiv.height(); + dummyDiv.remove(); + } + } + } + else if (w == null || h == null) { + // calculate y label dimensions + for (i = 0; i < ticks.length; ++i) { + l = ticks[i].label; + if (l) + labels.push('
' + l + '
'); + } + + if (labels.length > 0) { + dummyDiv = makeDummyDiv(labels, ""); + if (w == null) + w = dummyDiv.children().width(); + if (h == null) + h = dummyDiv.find("div.tickLabel").height(); + dummyDiv.remove(); + } + } + + if (w == null) + w = 0; + if (h == null) + h = 0; + + axis.labelWidth = w; + axis.labelHeight = h; + } + + function allocateAxisBoxFirstPhase(axis) { + // find the bounding box of the axis by looking at label + // widths/heights and ticks, make room by diminishing the + // plotOffset + + var lw = axis.labelWidth, + lh = axis.labelHeight, + pos = axis.options.position, + tickLength = axis.options.tickLength, + axismargin = options.grid.axisMargin, + padding = options.grid.labelMargin, + all = axis.direction == "x" ? xaxes : yaxes, + index; + + // determine axis margin + var samePosition = $.grep(all, function (a) { + return a && a.options.position == pos && a.reserveSpace; + }); + if ($.inArray(axis, samePosition) == samePosition.length - 1) + axismargin = 0; // outermost + + // determine tick length - if we're innermost, we can use "full" + if (tickLength == null) + tickLength = "full"; + + var sameDirection = $.grep(all, function (a) { + return a && a.reserveSpace; + }); + + var innermost = $.inArray(axis, sameDirection) == 0; + if (!innermost && tickLength == "full") + tickLength = 5; + + if (!isNaN(+tickLength)) + padding += +tickLength; + + // compute box + if (axis.direction == "x") { + lh += padding; + + if (pos == "bottom") { + plotOffset.bottom += lh + axismargin; + axis.box = { top: canvasHeight - plotOffset.bottom, height: lh }; + } + else { + axis.box = { top: plotOffset.top + axismargin, height: lh }; + plotOffset.top += lh + axismargin; + } + } + else { + lw += padding; + + if (pos == "left") { + axis.box = { left: plotOffset.left + axismargin, width: lw }; + plotOffset.left += lw + axismargin; + } + else { + plotOffset.right += lw + axismargin; + axis.box = { left: canvasWidth - plotOffset.right, width: lw }; + } + } + + // save for future reference + axis.position = pos; + axis.tickLength = tickLength; + axis.box.padding = padding; + axis.innermost = innermost; + } + + function allocateAxisBoxSecondPhase(axis) { + // set remaining bounding box coordinates + if (axis.direction == "x") { + axis.box.left = plotOffset.left; + axis.box.width = plotWidth; + } + else { + axis.box.top = plotOffset.top; + axis.box.height = plotHeight; + } + } + + function setupGrid() { + var i, axes = allAxes(); + + // first calculate the plot and axis box dimensions + + $.each(axes, function (_, axis) { + axis.show = axis.options.show; + if (axis.show == null) + axis.show = axis.used; // by default an axis is visible if it's got data + + axis.reserveSpace = axis.show || axis.options.reserveSpace; + + setRange(axis); + }); + + allocatedAxes = $.grep(axes, function (axis) { return axis.reserveSpace; }); + + plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = 0; + if (options.grid.show) { + $.each(allocatedAxes, function (_, axis) { + // make the ticks + setupTickGeneration(axis); + setTicks(axis); + snapRangeToTicks(axis, axis.ticks); + + // find labelWidth/Height for axis + measureTickLabels(axis); + }); + + // with all dimensions in house, we can compute the + // axis boxes, start from the outside (reverse order) + for (i = allocatedAxes.length - 1; i >= 0; --i) + allocateAxisBoxFirstPhase(allocatedAxes[i]); + + // make sure we've got enough space for things that + // might stick out + var minMargin = options.grid.minBorderMargin; + if (minMargin == null) { + minMargin = 0; + for (i = 0; i < series.length; ++i) + minMargin = Math.max(minMargin, series[i].points.radius + series[i].points.lineWidth/2); + } + + for (var a in plotOffset) { + plotOffset[a] += options.grid.borderWidth; + plotOffset[a] = Math.max(minMargin, plotOffset[a]); + } + } + + plotWidth = canvasWidth - plotOffset.left - plotOffset.right; + plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top; + + // now we got the proper plotWidth/Height, we can compute the scaling + $.each(axes, function (_, axis) { + setTransformationHelpers(axis); + }); + + if (options.grid.show) { + $.each(allocatedAxes, function (_, axis) { + allocateAxisBoxSecondPhase(axis); + }); + + insertAxisLabels(); + } + + insertLegend(); + } + + function setRange(axis) { + var opts = axis.options, + min = +(opts.min != null ? opts.min : axis.datamin), + max = +(opts.max != null ? opts.max : axis.datamax), + delta = max - min; + + if (delta == 0.0) { + // degenerate case + var widen = max == 0 ? 1 : 0.01; + + if (opts.min == null) + min -= widen; + // always widen max if we couldn't widen min to ensure we + // don't fall into min == max which doesn't work + if (opts.max == null || opts.min != null) + max += widen; + } + else { + // consider autoscaling + var margin = opts.autoscaleMargin; + if (margin != null) { + if (opts.min == null) { + min -= delta * margin; + // make sure we don't go below zero if all values + // are positive + if (min < 0 && axis.datamin != null && axis.datamin >= 0) + min = 0; + } + if (opts.max == null) { + max += delta * margin; + if (max > 0 && axis.datamax != null && axis.datamax <= 0) + max = 0; + } + } + } + axis.min = min; + axis.max = max; + } + + function setupTickGeneration(axis) { + var opts = axis.options; + + // estimate number of ticks + var noTicks; + if (typeof opts.ticks == "number" && opts.ticks > 0) + noTicks = opts.ticks; + else + // heuristic based on the model a*sqrt(x) fitted to + // some data points that seemed reasonable + noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? canvasWidth : canvasHeight); + + var delta = (axis.max - axis.min) / noTicks, + size, generator, unit, formatter, i, magn, norm; + + if (opts.mode == "time") { + // pretty handling of time + + // map of app. size of time units in milliseconds + var timeUnitSize = { + "second": 1000, + "minute": 60 * 1000, + "hour": 60 * 60 * 1000, + "day": 24 * 60 * 60 * 1000, + "month": 30 * 24 * 60 * 60 * 1000, + "year": 365.2425 * 24 * 60 * 60 * 1000 + }; + + + // the allowed tick sizes, after 1 year we use + // an integer algorithm + var spec = [ + [1, "second"], [2, "second"], [5, "second"], [10, "second"], + [30, "second"], + [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"], + [30, "minute"], + [1, "hour"], [2, "hour"], [4, "hour"], + [8, "hour"], [12, "hour"], + [1, "day"], [2, "day"], [3, "day"], + [0.25, "month"], [0.5, "month"], [1, "month"], + [2, "month"], [3, "month"], [6, "month"], + [1, "year"] + ]; + + var minSize = 0; + if (opts.minTickSize != null) { + if (typeof opts.tickSize == "number") + minSize = opts.tickSize; + else + minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]]; + } + + for (var i = 0; i < spec.length - 1; ++i) + if (delta < (spec[i][0] * timeUnitSize[spec[i][1]] + + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2 + && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) + break; + size = spec[i][0]; + unit = spec[i][1]; + + // special-case the possibility of several years + if (unit == "year") { + magn = Math.pow(10, Math.floor(Math.log(delta / timeUnitSize.year) / Math.LN10)); + norm = (delta / timeUnitSize.year) / magn; + if (norm < 1.5) + size = 1; + else if (norm < 3) + size = 2; + else if (norm < 7.5) + size = 5; + else + size = 10; + + size *= magn; + } + + axis.tickSize = opts.tickSize || [size, unit]; + + generator = function(axis) { + var ticks = [], + tickSize = axis.tickSize[0], unit = axis.tickSize[1], + d = new Date(axis.min); + + var step = tickSize * timeUnitSize[unit]; + + if (unit == "second") + d.setUTCSeconds(floorInBase(d.getUTCSeconds(), tickSize)); + if (unit == "minute") + d.setUTCMinutes(floorInBase(d.getUTCMinutes(), tickSize)); + if (unit == "hour") + d.setUTCHours(floorInBase(d.getUTCHours(), tickSize)); + if (unit == "month") + d.setUTCMonth(floorInBase(d.getUTCMonth(), tickSize)); + if (unit == "year") + d.setUTCFullYear(floorInBase(d.getUTCFullYear(), tickSize)); + + // reset smaller components + d.setUTCMilliseconds(0); + if (step >= timeUnitSize.minute) + d.setUTCSeconds(0); + if (step >= timeUnitSize.hour) + d.setUTCMinutes(0); + if (step >= timeUnitSize.day) + d.setUTCHours(0); + if (step >= timeUnitSize.day * 4) + d.setUTCDate(1); + if (step >= timeUnitSize.year) + d.setUTCMonth(0); + + + var carry = 0, v = Number.NaN, prev; + do { + prev = v; + v = d.getTime(); + ticks.push(v); + if (unit == "month") { + if (tickSize < 1) { + // a bit complicated - we'll divide the month + // up but we need to take care of fractions + // so we don't end up in the middle of a day + d.setUTCDate(1); + var start = d.getTime(); + d.setUTCMonth(d.getUTCMonth() + 1); + var end = d.getTime(); + d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize); + carry = d.getUTCHours(); + d.setUTCHours(0); + } + else + d.setUTCMonth(d.getUTCMonth() + tickSize); + } + else if (unit == "year") { + d.setUTCFullYear(d.getUTCFullYear() + tickSize); + } + else + d.setTime(v + step); + } while (v < axis.max && v != prev); + + return ticks; + }; + + formatter = function (v, axis) { + var d = new Date(v); + + // first check global format + if (opts.timeformat != null) + return $.plot.formatDate(d, opts.timeformat, opts.monthNames); + + var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]]; + var span = axis.max - axis.min; + var suffix = (opts.twelveHourClock) ? " %p" : ""; + + if (t < timeUnitSize.minute) + fmt = "%h:%M:%S" + suffix; + else if (t < timeUnitSize.day) { + if (span < 2 * timeUnitSize.day) + fmt = "%h:%M" + suffix; + else + fmt = "%b %d %h:%M" + suffix; + } + else if (t < timeUnitSize.month) + fmt = "%b %d"; + else if (t < timeUnitSize.year) { + if (span < timeUnitSize.year) + fmt = "%b"; + else + fmt = "%b %y"; + } + else + fmt = "%y"; + + return $.plot.formatDate(d, fmt, opts.monthNames); + }; + } + else { + // pretty rounding of base-10 numbers + var maxDec = opts.tickDecimals; + var dec = -Math.floor(Math.log(delta) / Math.LN10); + if (maxDec != null && dec > maxDec) + dec = maxDec; + + magn = Math.pow(10, -dec); + norm = delta / magn; // norm is between 1.0 and 10.0 + + if (norm < 1.5) + size = 1; + else if (norm < 3) { + size = 2; + // special case for 2.5, requires an extra decimal + if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) { + size = 2.5; + ++dec; + } + } + else if (norm < 7.5) + size = 5; + else + size = 10; + + size *= magn; + + if (opts.minTickSize != null && size < opts.minTickSize) + size = opts.minTickSize; + + axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec); + axis.tickSize = opts.tickSize || size; + + generator = function (axis) { + var ticks = []; + + // spew out all possible ticks + var start = floorInBase(axis.min, axis.tickSize), + i = 0, v = Number.NaN, prev; + do { + prev = v; + v = start + i * axis.tickSize; + ticks.push(v); + ++i; + } while (v < axis.max && v != prev); + return ticks; + }; + + formatter = function (v, axis) { + return v.toFixed(axis.tickDecimals); + }; + } + + if (opts.alignTicksWithAxis != null) { + var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1]; + if (otherAxis && otherAxis.used && otherAxis != axis) { + // consider snapping min/max to outermost nice ticks + var niceTicks = generator(axis); + if (niceTicks.length > 0) { + if (opts.min == null) + axis.min = Math.min(axis.min, niceTicks[0]); + if (opts.max == null && niceTicks.length > 1) + axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]); + } + + generator = function (axis) { + // copy ticks, scaled to this axis + var ticks = [], v, i; + for (i = 0; i < otherAxis.ticks.length; ++i) { + v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min); + v = axis.min + v * (axis.max - axis.min); + ticks.push(v); + } + return ticks; + }; + + // we might need an extra decimal since forced + // ticks don't necessarily fit naturally + if (axis.mode != "time" && opts.tickDecimals == null) { + var extraDec = Math.max(0, -Math.floor(Math.log(delta) / Math.LN10) + 1), + ts = generator(axis); + + // only proceed if the tick interval rounded + // with an extra decimal doesn't give us a + // zero at end + if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec)))) + axis.tickDecimals = extraDec; + } + } + } + + axis.tickGenerator = generator; + if ($.isFunction(opts.tickFormatter)) + axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); }; + else + axis.tickFormatter = formatter; + } + + function setTicks(axis) { + var oticks = axis.options.ticks, ticks = []; + if (oticks == null || (typeof oticks == "number" && oticks > 0)) + ticks = axis.tickGenerator(axis); + else if (oticks) { + if ($.isFunction(oticks)) + // generate the ticks + ticks = oticks({ min: axis.min, max: axis.max }); + else + ticks = oticks; + } + + // clean up/labelify the supplied ticks, copy them over + var i, v; + axis.ticks = []; + for (i = 0; i < ticks.length; ++i) { + var label = null; + var t = ticks[i]; + if (typeof t == "object") { + v = +t[0]; + if (t.length > 1) + label = t[1]; + } + else + v = +t; + if (label == null) + label = axis.tickFormatter(v, axis); + if (!isNaN(v)) + axis.ticks.push({ v: v, label: label }); + } + } + + function snapRangeToTicks(axis, ticks) { + if (axis.options.autoscaleMargin && ticks.length > 0) { + // snap to ticks + if (axis.options.min == null) + axis.min = Math.min(axis.min, ticks[0].v); + if (axis.options.max == null && ticks.length > 1) + axis.max = Math.max(axis.max, ticks[ticks.length - 1].v); + } + } + + function draw() { + ctx.clearRect(0, 0, canvasWidth, canvasHeight); + + var grid = options.grid; + + // draw background, if any + if (grid.show && grid.backgroundColor) + drawBackground(); + + if (grid.show && !grid.aboveData) + drawGrid(); + + for (var i = 0; i < series.length; ++i) { + executeHooks(hooks.drawSeries, [ctx, series[i]]); + drawSeries(series[i]); + } + + executeHooks(hooks.draw, [ctx]); + + if (grid.show && grid.aboveData) + drawGrid(); + } + + function extractRange(ranges, coord) { + var axis, from, to, key, axes = allAxes(); + + for (i = 0; i < axes.length; ++i) { + axis = axes[i]; + if (axis.direction == coord) { + key = coord + axis.n + "axis"; + if (!ranges[key] && axis.n == 1) + key = coord + "axis"; // support x1axis as xaxis + if (ranges[key]) { + from = ranges[key].from; + to = ranges[key].to; + break; + } + } + } + + // backwards-compat stuff - to be removed in future + if (!ranges[key]) { + axis = coord == "x" ? xaxes[0] : yaxes[0]; + from = ranges[coord + "1"]; + to = ranges[coord + "2"]; + } + + // auto-reverse as an added bonus + if (from != null && to != null && from > to) { + var tmp = from; + from = to; + to = tmp; + } + + return { from: from, to: to, axis: axis }; + } + + function drawBackground() { + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)"); + ctx.fillRect(0, 0, plotWidth, plotHeight); + ctx.restore(); + } + + function drawGrid() { + var i; + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + // draw markings + var markings = options.grid.markings; + if (markings) { + if ($.isFunction(markings)) { + var axes = plot.getAxes(); + // xmin etc. is backwards compatibility, to be + // removed in the future + axes.xmin = axes.xaxis.min; + axes.xmax = axes.xaxis.max; + axes.ymin = axes.yaxis.min; + axes.ymax = axes.yaxis.max; + + markings = markings(axes); + } + + for (i = 0; i < markings.length; ++i) { + var m = markings[i], + xrange = extractRange(m, "x"), + yrange = extractRange(m, "y"); + + // fill in missing + if (xrange.from == null) + xrange.from = xrange.axis.min; + if (xrange.to == null) + xrange.to = xrange.axis.max; + if (yrange.from == null) + yrange.from = yrange.axis.min; + if (yrange.to == null) + yrange.to = yrange.axis.max; + + // clip + if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max || + yrange.to < yrange.axis.min || yrange.from > yrange.axis.max) + continue; + + xrange.from = Math.max(xrange.from, xrange.axis.min); + xrange.to = Math.min(xrange.to, xrange.axis.max); + yrange.from = Math.max(yrange.from, yrange.axis.min); + yrange.to = Math.min(yrange.to, yrange.axis.max); + + if (xrange.from == xrange.to && yrange.from == yrange.to) + continue; + + // then draw + xrange.from = xrange.axis.p2c(xrange.from); + xrange.to = xrange.axis.p2c(xrange.to); + yrange.from = yrange.axis.p2c(yrange.from); + yrange.to = yrange.axis.p2c(yrange.to); + + if (xrange.from == xrange.to || yrange.from == yrange.to) { + // draw line + ctx.beginPath(); + ctx.strokeStyle = m.color || options.grid.markingsColor; + ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth; + ctx.moveTo(xrange.from, yrange.from); + ctx.lineTo(xrange.to, yrange.to); + ctx.stroke(); + } + else { + // fill area + ctx.fillStyle = m.color || options.grid.markingsColor; + ctx.fillRect(xrange.from, yrange.to, + xrange.to - xrange.from, + yrange.from - yrange.to); + } + } + } + + // draw the ticks + var axes = allAxes(), bw = options.grid.borderWidth; + + for (var j = 0; j < axes.length; ++j) { + var axis = axes[j], box = axis.box, + t = axis.tickLength, x, y, xoff, yoff; + if (!axis.show || axis.ticks.length == 0) + continue + + ctx.strokeStyle = axis.options.tickColor || $.color.parse(axis.options.color).scale('a', 0.22).toString(); + ctx.lineWidth = 1; + + // find the edges + if (axis.direction == "x") { + x = 0; + if (t == "full") + y = (axis.position == "top" ? 0 : plotHeight); + else + y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0); + } + else { + y = 0; + if (t == "full") + x = (axis.position == "left" ? 0 : plotWidth); + else + x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0); + } + + // draw tick bar + if (!axis.innermost) { + ctx.beginPath(); + xoff = yoff = 0; + if (axis.direction == "x") + xoff = plotWidth; + else + yoff = plotHeight; + + if (ctx.lineWidth == 1) { + x = Math.floor(x) + 0.5; + y = Math.floor(y) + 0.5; + } + + ctx.moveTo(x, y); + ctx.lineTo(x + xoff, y + yoff); + ctx.stroke(); + } + + // draw ticks + ctx.beginPath(); + for (i = 0; i < axis.ticks.length; ++i) { + var v = axis.ticks[i].v; + + xoff = yoff = 0; + + if (v < axis.min || v > axis.max + // skip those lying on the axes if we got a border + || (t == "full" && bw > 0 + && (v == axis.min || v == axis.max))) + continue; + + if (axis.direction == "x") { + x = axis.p2c(v); + yoff = t == "full" ? -plotHeight : t; + + if (axis.position == "top") + yoff = -yoff; + } + else { + y = axis.p2c(v); + xoff = t == "full" ? -plotWidth : t; + + if (axis.position == "left") + xoff = -xoff; + } + + if (ctx.lineWidth == 1) { + if (axis.direction == "x") + x = Math.floor(x) + 0.5; + else + y = Math.floor(y) + 0.5; + } + + ctx.moveTo(x, y); + ctx.lineTo(x + xoff, y + yoff); + } + + ctx.stroke(); + } + + + // draw border + if (bw) { + ctx.lineWidth = bw; + ctx.strokeStyle = options.grid.borderColor; + ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw); + } + + ctx.restore(); + } + + function insertAxisLabels() { + placeholder.find(".tickLabels").remove(); + + var html = ['
']; + + var axes = allAxes(); + for (var j = 0; j < axes.length; ++j) { + var axis = axes[j], box = axis.box; + if (!axis.show) + continue; + //debug: html.push('
') + html.push('
'); + for (var i = 0; i < axis.ticks.length; ++i) { + var tick = axis.ticks[i]; + if (!tick.label || tick.v < axis.min || tick.v > axis.max) + continue; + + var pos = {}, align; + + if (axis.direction == "x") { + align = "center"; + pos.left = Math.round(plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2); + if (axis.position == "bottom") + pos.top = box.top + box.padding; + else + pos.bottom = canvasHeight - (box.top + box.height - box.padding); + } + else { + pos.top = Math.round(plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2); + if (axis.position == "left") { + pos.right = canvasWidth - (box.left + box.width - box.padding) + align = "right"; + } + else { + pos.left = box.left + box.padding; + align = "left"; + } + } + + pos.width = axis.labelWidth; + + var style = ["position:absolute", "text-align:" + align ]; + for (var a in pos) + style.push(a + ":" + pos[a] + "px") + + html.push('
' + tick.label + '
'); + } + html.push('
'); + } + + html.push('
'); + + placeholder.append(html.join("")); + } + + function drawSeries(series) { + if (series.lines.show) + drawSeriesLines(series); + if (series.bars.show) + drawSeriesBars(series); + if (series.points.show) + drawSeriesPoints(series); + } + + function drawSeriesLines(series) { + function plotLine(datapoints, xoffset, yoffset, axisx, axisy) { + var points = datapoints.points, + ps = datapoints.pointsize, + prevx = null, prevy = null; + + ctx.beginPath(); + for (var i = ps; i < points.length; i += ps) { + var x1 = points[i - ps], y1 = points[i - ps + 1], + x2 = points[i], y2 = points[i + 1]; + + if (x1 == null || x2 == null) + continue; + + // clip with ymin + if (y1 <= y2 && y1 < axisy.min) { + if (y2 < axisy.min) + continue; // line segment is outside + // compute new intersection point + x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; + y1 = axisy.min; + } + else if (y2 <= y1 && y2 < axisy.min) { + if (y1 < axisy.min) + continue; + x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; + y2 = axisy.min; + } + + // clip with ymax + if (y1 >= y2 && y1 > axisy.max) { + if (y2 > axisy.max) + continue; + x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; + y1 = axisy.max; + } + else if (y2 >= y1 && y2 > axisy.max) { + if (y1 > axisy.max) + continue; + x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; + y2 = axisy.max; + } + + // clip with xmin + if (x1 <= x2 && x1 < axisx.min) { + if (x2 < axisx.min) + continue; + y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; + x1 = axisx.min; + } + else if (x2 <= x1 && x2 < axisx.min) { + if (x1 < axisx.min) + continue; + y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; + x2 = axisx.min; + } + + // clip with xmax + if (x1 >= x2 && x1 > axisx.max) { + if (x2 > axisx.max) + continue; + y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; + x1 = axisx.max; + } + else if (x2 >= x1 && x2 > axisx.max) { + if (x1 > axisx.max) + continue; + y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; + x2 = axisx.max; + } + + if (x1 != prevx || y1 != prevy) + ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset); + + prevx = x2; + prevy = y2; + ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset); + } + ctx.stroke(); + } + + function plotLineArea(datapoints, axisx, axisy) { + var points = datapoints.points, + ps = datapoints.pointsize, + bottom = Math.min(Math.max(0, axisy.min), axisy.max), + i = 0, top, areaOpen = false, + ypos = 1, segmentStart = 0, segmentEnd = 0; + + // we process each segment in two turns, first forward + // direction to sketch out top, then once we hit the + // end we go backwards to sketch the bottom + while (true) { + if (ps > 0 && i > points.length + ps) + break; + + i += ps; // ps is negative if going backwards + + var x1 = points[i - ps], + y1 = points[i - ps + ypos], + x2 = points[i], y2 = points[i + ypos]; + + if (areaOpen) { + if (ps > 0 && x1 != null && x2 == null) { + // at turning point + segmentEnd = i; + ps = -ps; + ypos = 2; + continue; + } + + if (ps < 0 && i == segmentStart + ps) { + // done with the reverse sweep + ctx.fill(); + areaOpen = false; + ps = -ps; + ypos = 1; + i = segmentStart = segmentEnd + ps; + continue; + } + } + + if (x1 == null || x2 == null) + continue; + + // clip x values + + // clip with xmin + if (x1 <= x2 && x1 < axisx.min) { + if (x2 < axisx.min) + continue; + y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; + x1 = axisx.min; + } + else if (x2 <= x1 && x2 < axisx.min) { + if (x1 < axisx.min) + continue; + y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; + x2 = axisx.min; + } + + // clip with xmax + if (x1 >= x2 && x1 > axisx.max) { + if (x2 > axisx.max) + continue; + y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; + x1 = axisx.max; + } + else if (x2 >= x1 && x2 > axisx.max) { + if (x1 > axisx.max) + continue; + y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; + x2 = axisx.max; + } + + if (!areaOpen) { + // open area + ctx.beginPath(); + ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom)); + areaOpen = true; + } + + // now first check the case where both is outside + if (y1 >= axisy.max && y2 >= axisy.max) { + ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max)); + ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max)); + continue; + } + else if (y1 <= axisy.min && y2 <= axisy.min) { + ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min)); + ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min)); + continue; + } + + // else it's a bit more complicated, there might + // be a flat maxed out rectangle first, then a + // triangular cutout or reverse; to find these + // keep track of the current x values + var x1old = x1, x2old = x2; + + // clip the y values, without shortcutting, we + // go through all cases in turn + + // clip with ymin + if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) { + x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; + y1 = axisy.min; + } + else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) { + x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; + y2 = axisy.min; + } + + // clip with ymax + if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) { + x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; + y1 = axisy.max; + } + else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) { + x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; + y2 = axisy.max; + } + + // if the x value was changed we got a rectangle + // to fill + if (x1 != x1old) { + ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1)); + // it goes to (x1, y1), but we fill that below + } + + // fill triangular section, this sometimes result + // in redundant points if (x1, y1) hasn't changed + // from previous line to, but we just ignore that + ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1)); + ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); + + // fill the other rectangle if it's there + if (x2 != x2old) { + ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); + ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2)); + } + } + } + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + ctx.lineJoin = "round"; + + var lw = series.lines.lineWidth, + sw = series.shadowSize; + // FIXME: consider another form of shadow when filling is turned on + if (lw > 0 && sw > 0) { + // draw shadow as a thick and thin line with transparency + ctx.lineWidth = sw; + ctx.strokeStyle = "rgba(0,0,0,0.1)"; + // position shadow at angle from the mid of line + var angle = Math.PI/18; + plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis); + ctx.lineWidth = sw/2; + plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis); + } + + ctx.lineWidth = lw; + ctx.strokeStyle = series.color; + var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight); + if (fillStyle) { + ctx.fillStyle = fillStyle; + plotLineArea(series.datapoints, series.xaxis, series.yaxis); + } + + if (lw > 0) + plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis); + ctx.restore(); + } + + function drawSeriesPoints(series) { + function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) { + var points = datapoints.points, ps = datapoints.pointsize; + + for (var i = 0; i < points.length; i += ps) { + var x = points[i], y = points[i + 1]; + if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) + continue; + + ctx.beginPath(); + x = axisx.p2c(x); + y = axisy.p2c(y) + offset; + if (symbol == "circle") + ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false); + else + symbol(ctx, x, y, radius, shadow); + ctx.closePath(); + + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + ctx.stroke(); + } + } + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + var lw = series.points.lineWidth, + sw = series.shadowSize, + radius = series.points.radius, + symbol = series.points.symbol; + if (lw > 0 && sw > 0) { + // draw shadow in two steps + var w = sw / 2; + ctx.lineWidth = w; + ctx.strokeStyle = "rgba(0,0,0,0.1)"; + plotPoints(series.datapoints, radius, null, w + w/2, true, + series.xaxis, series.yaxis, symbol); + + ctx.strokeStyle = "rgba(0,0,0,0.2)"; + plotPoints(series.datapoints, radius, null, w/2, true, + series.xaxis, series.yaxis, symbol); + } + + ctx.lineWidth = lw; + ctx.strokeStyle = series.color; + plotPoints(series.datapoints, radius, + getFillStyle(series.points, series.color), 0, false, + series.xaxis, series.yaxis, symbol); + ctx.restore(); + } + + function drawBar(x, y, b, barLeft, barRight, offset, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) { + var left, right, bottom, top, + drawLeft, drawRight, drawTop, drawBottom, + tmp; + + // in horizontal mode, we start the bar from the left + // instead of from the bottom so it appears to be + // horizontal rather than vertical + if (horizontal) { + drawBottom = drawRight = drawTop = true; + drawLeft = false; + left = b; + right = x; + top = y + barLeft; + bottom = y + barRight; + + // account for negative bars + if (right < left) { + tmp = right; + right = left; + left = tmp; + drawLeft = true; + drawRight = false; + } + } + else { + drawLeft = drawRight = drawTop = true; + drawBottom = false; + left = x + barLeft; + right = x + barRight; + bottom = b; + top = y; + + // account for negative bars + if (top < bottom) { + tmp = top; + top = bottom; + bottom = tmp; + drawBottom = true; + drawTop = false; + } + } + + // clip + if (right < axisx.min || left > axisx.max || + top < axisy.min || bottom > axisy.max) + return; + + if (left < axisx.min) { + left = axisx.min; + drawLeft = false; + } + + if (right > axisx.max) { + right = axisx.max; + drawRight = false; + } + + if (bottom < axisy.min) { + bottom = axisy.min; + drawBottom = false; + } + + if (top > axisy.max) { + top = axisy.max; + drawTop = false; + } + + left = axisx.p2c(left); + bottom = axisy.p2c(bottom); + right = axisx.p2c(right); + top = axisy.p2c(top); + + // fill the bar + if (fillStyleCallback) { + c.beginPath(); + c.moveTo(left, bottom); + c.lineTo(left, top); + c.lineTo(right, top); + c.lineTo(right, bottom); + c.fillStyle = fillStyleCallback(bottom, top); + c.fill(); + } + + // draw outline + if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) { + c.beginPath(); + + // FIXME: inline moveTo is buggy with excanvas + c.moveTo(left, bottom + offset); + if (drawLeft) + c.lineTo(left, top + offset); + else + c.moveTo(left, top + offset); + if (drawTop) + c.lineTo(right, top + offset); + else + c.moveTo(right, top + offset); + if (drawRight) + c.lineTo(right, bottom + offset); + else + c.moveTo(right, bottom + offset); + if (drawBottom) + c.lineTo(left, bottom + offset); + else + c.moveTo(left, bottom + offset); + c.stroke(); + } + } + + function drawSeriesBars(series) { + function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) { + var points = datapoints.points, ps = datapoints.pointsize; + + for (var i = 0; i < points.length; i += ps) { + if (points[i] == null) + continue; + drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, offset, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth); + } + } + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + // FIXME: figure out a way to add shadows (for instance along the right edge) + ctx.lineWidth = series.bars.lineWidth; + ctx.strokeStyle = series.color; + var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2; + var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null; + plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis); + ctx.restore(); + } + + function getFillStyle(filloptions, seriesColor, bottom, top) { + var fill = filloptions.fill; + if (!fill) + return null; + + if (filloptions.fillColor) + return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor); + + var c = $.color.parse(seriesColor); + c.a = typeof fill == "number" ? fill : 0.4; + c.normalize(); + return c.toString(); + } + + function insertLegend() { + placeholder.find(".legend").remove(); + + if (!options.legend.show) + return; + + var fragments = [], rowStarted = false, + lf = options.legend.labelFormatter, s, label; + for (var i = 0; i < series.length; ++i) { + s = series[i]; + label = s.label; + if (!label) + continue; + + if (i % options.legend.noColumns == 0) { + if (rowStarted) + fragments.push(''); + fragments.push(''); + rowStarted = true; + } + + if (lf) + label = lf(label, s); + + fragments.push( + '
' + + '' + label + ''); + } + if (rowStarted) + fragments.push(''); + + if (fragments.length == 0) + return; + + var table = '' + fragments.join("") + '
'; + if (options.legend.container != null) + $(options.legend.container).html(table); + else { + var pos = "", + p = options.legend.position, + m = options.legend.margin; + if (m[0] == null) + m = [m, m]; + if (p.charAt(0) == "n") + pos += 'top:' + (m[1] + plotOffset.top) + 'px;'; + else if (p.charAt(0) == "s") + pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;'; + if (p.charAt(1) == "e") + pos += 'right:' + (m[0] + plotOffset.right) + 'px;'; + else if (p.charAt(1) == "w") + pos += 'left:' + (m[0] + plotOffset.left) + 'px;'; + var legend = $('
' + table.replace('style="', 'style="position:absolute;' + pos +';') + '
').appendTo(placeholder); + if (options.legend.backgroundOpacity != 0.0) { + // put in the transparent background + // separately to avoid blended labels and + // label boxes + var c = options.legend.backgroundColor; + if (c == null) { + c = options.grid.backgroundColor; + if (c && typeof c == "string") + c = $.color.parse(c); + else + c = $.color.extract(legend, 'background-color'); + c.a = 1; + c = c.toString(); + } + var div = legend.children(); + $('
').prependTo(legend).css('opacity', options.legend.backgroundOpacity); + } + } + } + + + // interactive features + + var highlights = [], + redrawTimeout = null; + + // returns the data item the mouse is over, or null if none is found + function findNearbyItem(mouseX, mouseY, seriesFilter) { + var maxDistance = options.grid.mouseActiveRadius, + smallestDistance = maxDistance * maxDistance + 1, + item = null, foundPoint = false, i, j; + + for (i = series.length - 1; i >= 0; --i) { + if (!seriesFilter(series[i])) + continue; + + var s = series[i], + axisx = s.xaxis, + axisy = s.yaxis, + points = s.datapoints.points, + ps = s.datapoints.pointsize, + mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster + my = axisy.c2p(mouseY), + maxx = maxDistance / axisx.scale, + maxy = maxDistance / axisy.scale; + + // with inverse transforms, we can't use the maxx/maxy + // optimization, sadly + if (axisx.options.inverseTransform) + maxx = Number.MAX_VALUE; + if (axisy.options.inverseTransform) + maxy = Number.MAX_VALUE; + + if (s.lines.show || s.points.show) { + for (j = 0; j < points.length; j += ps) { + var x = points[j], y = points[j + 1]; + if (x == null) + continue; + + // For points and lines, the cursor must be within a + // certain distance to the data point + if (x - mx > maxx || x - mx < -maxx || + y - my > maxy || y - my < -maxy) + continue; + + // We have to calculate distances in pixels, not in + // data units, because the scales of the axes may be different + var dx = Math.abs(axisx.p2c(x) - mouseX), + dy = Math.abs(axisy.p2c(y) - mouseY), + dist = dx * dx + dy * dy; // we save the sqrt + + // use <= to ensure last point takes precedence + // (last generally means on top of) + if (dist < smallestDistance) { + smallestDistance = dist; + item = [i, j / ps]; + } + } + } + + if (s.bars.show && !item) { // no other point can be nearby + var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2, + barRight = barLeft + s.bars.barWidth; + + for (j = 0; j < points.length; j += ps) { + var x = points[j], y = points[j + 1], b = points[j + 2]; + if (x == null) + continue; + + // for a bar graph, the cursor must be inside the bar + if (series[i].bars.horizontal ? + (mx <= Math.max(b, x) && mx >= Math.min(b, x) && + my >= y + barLeft && my <= y + barRight) : + (mx >= x + barLeft && mx <= x + barRight && + my >= Math.min(b, y) && my <= Math.max(b, y))) + item = [i, j / ps]; + } + } + } + + if (item) { + i = item[0]; + j = item[1]; + ps = series[i].datapoints.pointsize; + + return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps), + dataIndex: j, + series: series[i], + seriesIndex: i }; + } + + return null; + } + + function onMouseMove(e) { + if (options.grid.hoverable) + triggerClickHoverEvent("plothover", e, + function (s) { return s["hoverable"] != false; }); + } + + function onMouseLeave(e) { + if (options.grid.hoverable) + triggerClickHoverEvent("plothover", e, + function (s) { return false; }); + } + + function onClick(e) { + triggerClickHoverEvent("plotclick", e, + function (s) { return s["clickable"] != false; }); + } + + // trigger click or hover event (they send the same parameters + // so we share their code) + function triggerClickHoverEvent(eventname, event, seriesFilter) { + var offset = eventHolder.offset(), + canvasX = event.pageX - offset.left - plotOffset.left, + canvasY = event.pageY - offset.top - plotOffset.top, + pos = canvasToAxisCoords({ left: canvasX, top: canvasY }); + + pos.pageX = event.pageX; + pos.pageY = event.pageY; + + var item = findNearbyItem(canvasX, canvasY, seriesFilter); + + if (item) { + // fill in mouse pos for any listeners out there + item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left); + item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top); + } + + if (options.grid.autoHighlight) { + // clear auto-highlights + for (var i = 0; i < highlights.length; ++i) { + var h = highlights[i]; + if (h.auto == eventname && + !(item && h.series == item.series && + h.point[0] == item.datapoint[0] && + h.point[1] == item.datapoint[1])) + unhighlight(h.series, h.point); + } + + if (item) + highlight(item.series, item.datapoint, eventname); + } + + placeholder.trigger(eventname, [ pos, item ]); + } + + function triggerRedrawOverlay() { + if (!redrawTimeout) + redrawTimeout = setTimeout(drawOverlay, 30); + } + + function drawOverlay() { + redrawTimeout = null; + + // draw highlights + octx.save(); + octx.clearRect(0, 0, canvasWidth, canvasHeight); + octx.translate(plotOffset.left, plotOffset.top); + + var i, hi; + for (i = 0; i < highlights.length; ++i) { + hi = highlights[i]; + + if (hi.series.bars.show) + drawBarHighlight(hi.series, hi.point); + else + drawPointHighlight(hi.series, hi.point); + } + octx.restore(); + + executeHooks(hooks.drawOverlay, [octx]); + } + + function highlight(s, point, auto) { + if (typeof s == "number") + s = series[s]; + + if (typeof point == "number") { + var ps = s.datapoints.pointsize; + point = s.datapoints.points.slice(ps * point, ps * (point + 1)); + } + + var i = indexOfHighlight(s, point); + if (i == -1) { + highlights.push({ series: s, point: point, auto: auto }); + + triggerRedrawOverlay(); + } + else if (!auto) + highlights[i].auto = false; + } + + function unhighlight(s, point) { + if (s == null && point == null) { + highlights = []; + triggerRedrawOverlay(); + } + + if (typeof s == "number") + s = series[s]; + + if (typeof point == "number") + point = s.data[point]; + + var i = indexOfHighlight(s, point); + if (i != -1) { + highlights.splice(i, 1); + + triggerRedrawOverlay(); + } + } + + function indexOfHighlight(s, p) { + for (var i = 0; i < highlights.length; ++i) { + var h = highlights[i]; + if (h.series == s && h.point[0] == p[0] + && h.point[1] == p[1]) + return i; + } + return -1; + } + + function drawPointHighlight(series, point) { + var x = point[0], y = point[1], + axisx = series.xaxis, axisy = series.yaxis; + + if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) + return; + + var pointRadius = series.points.radius + series.points.lineWidth / 2; + octx.lineWidth = pointRadius; + octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString(); + var radius = 1.5 * pointRadius, + x = axisx.p2c(x), + y = axisy.p2c(y); + + octx.beginPath(); + if (series.points.symbol == "circle") + octx.arc(x, y, radius, 0, 2 * Math.PI, false); + else + series.points.symbol(octx, x, y, radius, false); + octx.closePath(); + octx.stroke(); + } + + function drawBarHighlight(series, point) { + octx.lineWidth = series.bars.lineWidth; + octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString(); + var fillStyle = $.color.parse(series.color).scale('a', 0.5).toString(); + var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2; + drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth, + 0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth); + } + + function getColorOrGradient(spec, bottom, top, defaultColor) { + if (typeof spec == "string") + return spec; + else { + // assume this is a gradient spec; IE currently only + // supports a simple vertical gradient properly, so that's + // what we support too + var gradient = ctx.createLinearGradient(0, top, 0, bottom); + + for (var i = 0, l = spec.colors.length; i < l; ++i) { + var c = spec.colors[i]; + if (typeof c != "string") { + var co = $.color.parse(defaultColor); + if (c.brightness != null) + co = co.scale('rgb', c.brightness) + if (c.opacity != null) + co.a *= c.opacity; + c = co.toString(); + } + gradient.addColorStop(i / (l - 1), c); + } + + return gradient; + } + } + } + + $.plot = function(placeholder, data, options) { + //var t0 = new Date(); + var plot = new Plot($(placeholder), data, options, $.plot.plugins); + //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime())); + return plot; + }; + + $.plot.version = "0.7"; + + $.plot.plugins = []; + + // returns a string with the date d formatted according to fmt + $.plot.formatDate = function(d, fmt, monthNames) { + var leftPad = function(n) { + n = "" + n; + return n.length == 1 ? "0" + n : n; + }; + + var r = []; + var escape = false, padNext = false; + var hours = d.getUTCHours(); + var isAM = hours < 12; + if (monthNames == null) + monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + + if (fmt.search(/%p|%P/) != -1) { + if (hours > 12) { + hours = hours - 12; + } else if (hours == 0) { + hours = 12; + } + } + for (var i = 0; i < fmt.length; ++i) { + var c = fmt.charAt(i); + + if (escape) { + switch (c) { + case 'h': c = "" + hours; break; + case 'H': c = leftPad(hours); break; + case 'M': c = leftPad(d.getUTCMinutes()); break; + case 'S': c = leftPad(d.getUTCSeconds()); break; + case 'd': c = "" + d.getUTCDate(); break; + case 'm': c = "" + (d.getUTCMonth() + 1); break; + case 'y': c = "" + d.getUTCFullYear(); break; + case 'b': c = "" + monthNames[d.getUTCMonth()]; break; + case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break; + case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break; + case '0': c = ""; padNext = true; break; + } + if (c && padNext) { + c = leftPad(c); + padNext = false; + } + r.push(c); + if (!padNext) + escape = false; + } + else { + if (c == "%") + escape = true; + else + r.push(c); + } + } + return r.join(""); + }; + + // round to nearby lower multiple of base + function floorInBase(n, base) { + return base * Math.floor(n / base); + } + +})(jQuery); diff --git a/cosmic-client/cosmic-ui/lib/flot/jquery.flot.navigate.js b/cosmic-client/cosmic-ui/lib/flot/jquery.flot.navigate.js new file mode 100644 index 0000000000..f2b97603c3 --- /dev/null +++ b/cosmic-client/cosmic-ui/lib/flot/jquery.flot.navigate.js @@ -0,0 +1,336 @@ +/* +Flot plugin for adding panning and zooming capabilities to a plot. + +The default behaviour is double click and scrollwheel up/down to zoom +in, drag to pan. The plugin defines plot.zoom({ center }), +plot.zoomOut() and plot.pan(offset) so you easily can add custom +controls. It also fires a "plotpan" and "plotzoom" event when +something happens, useful for synchronizing plots. + +Options: + + zoom: { + interactive: false + trigger: "dblclick" // or "click" for single click + amount: 1.5 // 2 = 200% (zoom in), 0.5 = 50% (zoom out) + } + + pan: { + interactive: false + cursor: "move" // CSS mouse cursor value used when dragging, e.g. "pointer" + frameRate: 20 + } + + xaxis, yaxis, x2axis, y2axis: { + zoomRange: null // or [number, number] (min range, max range) or false + panRange: null // or [number, number] (min, max) or false + } + +"interactive" enables the built-in drag/click behaviour. If you enable +interactive for pan, then you'll have a basic plot that supports +moving around; the same for zoom. + +"amount" specifies the default amount to zoom in (so 1.5 = 150%) +relative to the current viewport. + +"cursor" is a standard CSS mouse cursor string used for visual +feedback to the user when dragging. + +"frameRate" specifies the maximum number of times per second the plot +will update itself while the user is panning around on it (set to null +to disable intermediate pans, the plot will then not update until the +mouse button is released). + +"zoomRange" is the interval in which zooming can happen, e.g. with +zoomRange: [1, 100] the zoom will never scale the axis so that the +difference between min and max is smaller than 1 or larger than 100. +You can set either end to null to ignore, e.g. [1, null]. If you set +zoomRange to false, zooming on that axis will be disabled. + +"panRange" confines the panning to stay within a range, e.g. with +panRange: [-10, 20] panning stops at -10 in one end and at 20 in the +other. Either can be null, e.g. [-10, null]. If you set +panRange to false, panning on that axis will be disabled. + +Example API usage: + + plot = $.plot(...); + + // zoom default amount in on the pixel (10, 20) + plot.zoom({ center: { left: 10, top: 20 } }); + + // zoom out again + plot.zoomOut({ center: { left: 10, top: 20 } }); + + // zoom 200% in on the pixel (10, 20) + plot.zoom({ amount: 2, center: { left: 10, top: 20 } }); + + // pan 100 pixels to the left and 20 down + plot.pan({ left: -100, top: 20 }) + +Here, "center" specifies where the center of the zooming should +happen. Note that this is defined in pixel space, not the space of the +data points (you can use the p2c helpers on the axes in Flot to help +you convert between these). + +"amount" is the amount to zoom the viewport relative to the current +range, so 1 is 100% (i.e. no change), 1.5 is 150% (zoom in), 0.7 is +70% (zoom out). You can set the default in the options. + +*/ + + +// First two dependencies, jquery.event.drag.js and +// jquery.mousewheel.js, we put them inline here to save people the +// effort of downloading them. + +/* +jquery.event.drag.js ~ v1.5 ~ Copyright (c) 2008, Three Dub Media (http://threedubmedia.com) +Licensed under the MIT License ~ http://threedubmedia.googlecode.com/files/MIT-LICENSE.txt +*/ +(function(E){E.fn.drag=function(L,K,J){if(K){this.bind("dragstart",L)}if(J){this.bind("dragend",J)}return !L?this.trigger("drag"):this.bind("drag",K?K:L)};var A=E.event,B=A.special,F=B.drag={not:":input",distance:0,which:1,dragging:false,setup:function(J){J=E.extend({distance:F.distance,which:F.which,not:F.not},J||{});J.distance=I(J.distance);A.add(this,"mousedown",H,J);if(this.attachEvent){this.attachEvent("ondragstart",D)}},teardown:function(){A.remove(this,"mousedown",H);if(this===F.dragging){F.dragging=F.proxy=false}G(this,true);if(this.detachEvent){this.detachEvent("ondragstart",D)}}};B.dragstart=B.dragend={setup:function(){},teardown:function(){}};function H(L){var K=this,J,M=L.data||{};if(M.elem){K=L.dragTarget=M.elem;L.dragProxy=F.proxy||K;L.cursorOffsetX=M.pageX-M.left;L.cursorOffsetY=M.pageY-M.top;L.offsetX=L.pageX-L.cursorOffsetX;L.offsetY=L.pageY-L.cursorOffsetY}else{if(F.dragging||(M.which>0&&L.which!=M.which)||E(L.target).is(M.not)){return }}switch(L.type){case"mousedown":E.extend(M,E(K).offset(),{elem:K,target:L.target,pageX:L.pageX,pageY:L.pageY});A.add(document,"mousemove mouseup",H,M);G(K,false);F.dragging=null;return false;case !F.dragging&&"mousemove":if(I(L.pageX-M.pageX)+I(L.pageY-M.pageY) max) { + // make sure min < max + var tmp = min; + min = max; + max = tmp; + } + + var range = max - min; + if (zr && + ((zr[0] != null && range < zr[0]) || + (zr[1] != null && range > zr[1]))) + return; + + opts.min = min; + opts.max = max; + }); + + plot.setupGrid(); + plot.draw(); + + if (!args.preventEvent) + plot.getPlaceholder().trigger("plotzoom", [ plot ]); + } + + plot.pan = function (args) { + var delta = { + x: +args.left, + y: +args.top + }; + + if (isNaN(delta.x)) + delta.x = 0; + if (isNaN(delta.y)) + delta.y = 0; + + $.each(plot.getAxes(), function (_, axis) { + var opts = axis.options, + min, max, d = delta[axis.direction]; + + min = axis.c2p(axis.p2c(axis.min) + d), + max = axis.c2p(axis.p2c(axis.max) + d); + + var pr = opts.panRange; + if (pr === false) // no panning on this axis + return; + + if (pr) { + // check whether we hit the wall + if (pr[0] != null && pr[0] > min) { + d = pr[0] - min; + min += d; + max += d; + } + + if (pr[1] != null && pr[1] < max) { + d = pr[1] - max; + min += d; + max += d; + } + } + + opts.min = min; + opts.max = max; + }); + + plot.setupGrid(); + plot.draw(); + + if (!args.preventEvent) + plot.getPlaceholder().trigger("plotpan", [ plot ]); + } + + function shutdown(plot, eventHolder) { + eventHolder.unbind(plot.getOptions().zoom.trigger, onZoomClick); + eventHolder.unbind("mousewheel", onMouseWheel); + eventHolder.unbind("dragstart", onDragStart); + eventHolder.unbind("drag", onDrag); + eventHolder.unbind("dragend", onDragEnd); + if (panTimeout) + clearTimeout(panTimeout); + } + + plot.hooks.bindEvents.push(bindEvents); + plot.hooks.shutdown.push(shutdown); + } + + $.plot.plugins.push({ + init: init, + options: options, + name: 'navigate', + version: '1.3' + }); +})(jQuery); diff --git a/cosmic-client/cosmic-ui/lib/flot/jquery.flot.pie.js b/cosmic-client/cosmic-ui/lib/flot/jquery.flot.pie.js new file mode 100644 index 0000000000..b46c03c271 --- /dev/null +++ b/cosmic-client/cosmic-ui/lib/flot/jquery.flot.pie.js @@ -0,0 +1,750 @@ +/* +Flot plugin for rendering pie charts. The plugin assumes the data is +coming is as a single data value for each series, and each of those +values is a positive value or zero (negative numbers don't make +any sense and will cause strange effects). The data values do +NOT need to be passed in as percentage values because it +internally calculates the total and percentages. + +* Created by Brian Medendorp, June 2009 +* Updated November 2009 with contributions from: btburnett3, Anthony Aragues and Xavi Ivars + +* Changes: + 2009-10-22: lineJoin set to round + 2009-10-23: IE full circle fix, donut + 2009-11-11: Added basic hover from btburnett3 - does not work in IE, and center is off in Chrome and Opera + 2009-11-17: Added IE hover capability submitted by Anthony Aragues + 2009-11-18: Added bug fix submitted by Xavi Ivars (issues with arrays when other JS libraries are included as well) + + +Available options are: +series: { + pie: { + show: true/false + radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto' + innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect + startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result + tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show) + offset: { + top: integer value to move the pie up or down + left: integer value to move the pie left or right, or 'auto' + }, + stroke: { + color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF') + width: integer pixel width of the stroke + }, + label: { + show: true/false, or 'auto' + formatter: a user-defined function that modifies the text/style of the label text + radius: 0-1 for percentage of fullsize, or a specified pixel length + background: { + color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000') + opacity: 0-1 + }, + threshold: 0-1 for the percentage value at which to hide labels (if they're too small) + }, + combine: { + threshold: 0-1 for the percentage value at which to combine slices (if they're too small) + color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined + label: any text value of what the combined slice should be labeled + } + highlight: { + opacity: 0-1 + } + } +} + +More detail and specific examples can be found in the included HTML file. + +*/ + +(function ($) +{ + function init(plot) // this is the "body" of the plugin + { + var canvas = null; + var target = null; + var maxRadius = null; + var centerLeft = null; + var centerTop = null; + var total = 0; + var redraw = true; + var redrawAttempts = 10; + var shrink = 0.95; + var legendWidth = 0; + var processed = false; + var raw = false; + + // interactive variables + var highlights = []; + + // add hook to determine if pie plugin in enabled, and then perform necessary operations + plot.hooks.processOptions.push(checkPieEnabled); + plot.hooks.bindEvents.push(bindEvents); + + // check to see if the pie plugin is enabled + function checkPieEnabled(plot, options) + { + if (options.series.pie.show) + { + //disable grid + options.grid.show = false; + + // set labels.show + if (options.series.pie.label.show=='auto') + if (options.legend.show) + options.series.pie.label.show = false; + else + options.series.pie.label.show = true; + + // set radius + if (options.series.pie.radius=='auto') + if (options.series.pie.label.show) + options.series.pie.radius = 3/4; + else + options.series.pie.radius = 1; + + // ensure sane tilt + if (options.series.pie.tilt>1) + options.series.pie.tilt=1; + if (options.series.pie.tilt<0) + options.series.pie.tilt=0; + + // add processData hook to do transformations on the data + plot.hooks.processDatapoints.push(processDatapoints); + plot.hooks.drawOverlay.push(drawOverlay); + + // add draw hook + plot.hooks.draw.push(draw); + } + } + + // bind hoverable events + function bindEvents(plot, eventHolder) + { + var options = plot.getOptions(); + + if (options.series.pie.show && options.grid.hoverable) + eventHolder.unbind('mousemove').mousemove(onMouseMove); + + if (options.series.pie.show && options.grid.clickable) + eventHolder.unbind('click').click(onClick); + } + + + // debugging function that prints out an object + function alertObject(obj) + { + var msg = ''; + function traverse(obj, depth) + { + if (!depth) + depth = 0; + for (var i = 0; i < obj.length; ++i) + { + for (var j=0; jcanvas.width-maxRadius) + centerLeft = canvas.width-maxRadius; + } + + function fixData(data) + { + for (var i = 0; i < data.length; ++i) + { + if (typeof(data[i].data)=='number') + data[i].data = [[1,data[i].data]]; + else if (typeof(data[i].data)=='undefined' || typeof(data[i].data[0])=='undefined') + { + if (typeof(data[i].data)!='undefined' && typeof(data[i].data.label)!='undefined') + data[i].label = data[i].data.label; // fix weirdness coming from flot + data[i].data = [[1,0]]; + + } + } + return data; + } + + function combine(data) + { + data = fixData(data); + calcTotal(data); + var combined = 0; + var numCombined = 0; + var color = options.series.pie.combine.color; + + var newdata = []; + for (var i = 0; i < data.length; ++i) + { + // make sure its a number + data[i].data[0][1] = parseFloat(data[i].data[0][1]); + if (!data[i].data[0][1]) + data[i].data[0][1] = 0; + + if (data[i].data[0][1]/total<=options.series.pie.combine.threshold) + { + combined += data[i].data[0][1]; + numCombined++; + if (!color) + color = data[i].color; + } + else + { + newdata.push({ + data: [[1,data[i].data[0][1]]], + color: data[i].color, + label: data[i].label, + angle: (data[i].data[0][1]*(Math.PI*2))/total, + percent: (data[i].data[0][1]/total*100) + }); + } + } + if (numCombined>0) + newdata.push({ + data: [[1,combined]], + color: color, + label: options.series.pie.combine.label, + angle: (combined*(Math.PI*2))/total, + percent: (combined/total*100) + }); + return newdata; + } + + function draw(plot, newCtx) + { + if (!target) return; // if no series were passed + ctx = newCtx; + + setupPie(); + var slices = plot.getData(); + + var attempts = 0; + while (redraw && attempts0) + maxRadius *= shrink; + attempts += 1; + clear(); + if (options.series.pie.tilt<=0.8) + drawShadow(); + drawPie(); + } + if (attempts >= redrawAttempts) { + clear(); + target.prepend('
Could not draw pie with labels contained inside canvas
'); + } + + if ( plot.setSeries && plot.insertLegend ) + { + plot.setSeries(slices); + plot.insertLegend(); + } + + // we're actually done at this point, just defining internal functions at this point + + function clear() + { + ctx.clearRect(0,0,canvas.width,canvas.height); + target.children().filter('.pieLabel, .pieLabelBackground').remove(); + } + + function drawShadow() + { + var shadowLeft = 5; + var shadowTop = 15; + var edge = 10; + var alpha = 0.02; + + // set radius + if (options.series.pie.radius>1) + var radius = options.series.pie.radius; + else + var radius = maxRadius * options.series.pie.radius; + + if (radius>=(canvas.width/2)-shadowLeft || radius*options.series.pie.tilt>=(canvas.height/2)-shadowTop || radius<=edge) + return; // shadow would be outside canvas, so don't draw it + + ctx.save(); + ctx.translate(shadowLeft,shadowTop); + ctx.globalAlpha = alpha; + ctx.fillStyle = '#000'; + + // center and rotate to starting position + ctx.translate(centerLeft,centerTop); + ctx.scale(1, options.series.pie.tilt); + + //radius -= edge; + for (var i=1; i<=edge; i++) + { + ctx.beginPath(); + ctx.arc(0,0,radius,0,Math.PI*2,false); + ctx.fill(); + radius -= i; + } + + ctx.restore(); + } + + function drawPie() + { + startAngle = Math.PI*options.series.pie.startAngle; + + // set radius + if (options.series.pie.radius>1) + var radius = options.series.pie.radius; + else + var radius = maxRadius * options.series.pie.radius; + + // center and rotate to starting position + ctx.save(); + ctx.translate(centerLeft,centerTop); + ctx.scale(1, options.series.pie.tilt); + //ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera + + // draw slices + ctx.save(); + var currentAngle = startAngle; + for (var i = 0; i < slices.length; ++i) + { + slices[i].startAngle = currentAngle; + drawSlice(slices[i].angle, slices[i].color, true); + } + ctx.restore(); + + // draw slice outlines + ctx.save(); + ctx.lineWidth = options.series.pie.stroke.width; + currentAngle = startAngle; + for (var i = 0; i < slices.length; ++i) + drawSlice(slices[i].angle, options.series.pie.stroke.color, false); + ctx.restore(); + + // draw donut hole + drawDonutHole(ctx); + + // draw labels + if (options.series.pie.label.show) + drawLabels(); + + // restore to original state + ctx.restore(); + + function drawSlice(angle, color, fill) + { + if (angle<=0) + return; + + if (fill) + ctx.fillStyle = color; + else + { + ctx.strokeStyle = color; + ctx.lineJoin = 'round'; + } + + ctx.beginPath(); + if (Math.abs(angle - Math.PI*2) > 0.000000001) + ctx.moveTo(0,0); // Center of the pie + else if ($.browser.msie) + angle -= 0.0001; + //ctx.arc(0,0,radius,0,angle,false); // This doesn't work properly in Opera + ctx.arc(0,0,radius,currentAngle,currentAngle+angle,false); + ctx.closePath(); + //ctx.rotate(angle); // This doesn't work properly in Opera + currentAngle += angle; + + if (fill) + ctx.fill(); + else + ctx.stroke(); + } + + function drawLabels() + { + var currentAngle = startAngle; + + // set radius + if (options.series.pie.label.radius>1) + var radius = options.series.pie.label.radius; + else + var radius = maxRadius * options.series.pie.label.radius; + + for (var i = 0; i < slices.length; ++i) + { + if (slices[i].percent >= options.series.pie.label.threshold*100) + drawLabel(slices[i], currentAngle, i); + currentAngle += slices[i].angle; + } + + function drawLabel(slice, startAngle, index) + { + if (slice.data[0][1]==0) + return; + + // format label text + var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter; + if (lf) + text = lf(slice.label, slice); + else + text = slice.label; + if (plf) + text = plf(text, slice); + + var halfAngle = ((startAngle+slice.angle) + startAngle)/2; + var x = centerLeft + Math.round(Math.cos(halfAngle) * radius); + var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt; + + var html = '' + text + ""; + target.append(html); + var label = target.children('#pieLabel'+index); + var labelTop = (y - label.height()/2); + var labelLeft = (x - label.width()/2); + label.css('top', labelTop); + label.css('left', labelLeft); + + // check to make sure that the label is not outside the canvas + if (0-labelTop>0 || 0-labelLeft>0 || canvas.height-(labelTop+label.height())<0 || canvas.width-(labelLeft+label.width())<0) + redraw = true; + + if (options.series.pie.label.background.opacity != 0) { + // put in the transparent background separately to avoid blended labels and label boxes + var c = options.series.pie.label.background.color; + if (c == null) { + c = slice.color; + } + var pos = 'top:'+labelTop+'px;left:'+labelLeft+'px;'; + $('
').insertBefore(label).css('opacity', options.series.pie.label.background.opacity); + } + } // end individual label function + } // end drawLabels function + } // end drawPie function + } // end draw function + + // Placed here because it needs to be accessed from multiple locations + function drawDonutHole(layer) + { + // draw donut hole + if(options.series.pie.innerRadius > 0) + { + // subtract the center + layer.save(); + innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius; + layer.globalCompositeOperation = 'destination-out'; // this does not work with excanvas, but it will fall back to using the stroke color + layer.beginPath(); + layer.fillStyle = options.series.pie.stroke.color; + layer.arc(0,0,innerRadius,0,Math.PI*2,false); + layer.fill(); + layer.closePath(); + layer.restore(); + + // add inner stroke + layer.save(); + layer.beginPath(); + layer.strokeStyle = options.series.pie.stroke.color; + layer.arc(0,0,innerRadius,0,Math.PI*2,false); + layer.stroke(); + layer.closePath(); + layer.restore(); + // TODO: add extra shadow inside hole (with a mask) if the pie is tilted. + } + } + + //-- Additional Interactive related functions -- + + function isPointInPoly(poly, pt) + { + for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i) + ((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1])) + && (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0]) + && (c = !c); + return c; + } + + function findNearbySlice(mouseX, mouseY) + { + var slices = plot.getData(), + options = plot.getOptions(), + radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; + + for (var i = 0; i < slices.length; ++i) + { + var s = slices[i]; + + if(s.pie.show) + { + ctx.save(); + ctx.beginPath(); + ctx.moveTo(0,0); // Center of the pie + //ctx.scale(1, options.series.pie.tilt); // this actually seems to break everything when here. + ctx.arc(0,0,radius,s.startAngle,s.startAngle+s.angle,false); + ctx.closePath(); + x = mouseX-centerLeft; + y = mouseY-centerTop; + if(ctx.isPointInPath) + { + if (ctx.isPointInPath(mouseX-centerLeft, mouseY-centerTop)) + { + //alert('found slice!'); + ctx.restore(); + return {datapoint: [s.percent, s.data], dataIndex: 0, series: s, seriesIndex: i}; + } + } + else + { + // excanvas for IE doesn;t support isPointInPath, this is a workaround. + p1X = (radius * Math.cos(s.startAngle)); + p1Y = (radius * Math.sin(s.startAngle)); + p2X = (radius * Math.cos(s.startAngle+(s.angle/4))); + p2Y = (radius * Math.sin(s.startAngle+(s.angle/4))); + p3X = (radius * Math.cos(s.startAngle+(s.angle/2))); + p3Y = (radius * Math.sin(s.startAngle+(s.angle/2))); + p4X = (radius * Math.cos(s.startAngle+(s.angle/1.5))); + p4Y = (radius * Math.sin(s.startAngle+(s.angle/1.5))); + p5X = (radius * Math.cos(s.startAngle+s.angle)); + p5Y = (radius * Math.sin(s.startAngle+s.angle)); + arrPoly = [[0,0],[p1X,p1Y],[p2X,p2Y],[p3X,p3Y],[p4X,p4Y],[p5X,p5Y]]; + arrPoint = [x,y]; + // TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt? + if(isPointInPoly(arrPoly, arrPoint)) + { + ctx.restore(); + return {datapoint: [s.percent, s.data], dataIndex: 0, series: s, seriesIndex: i}; + } + } + ctx.restore(); + } + } + + return null; + } + + function onMouseMove(e) + { + triggerClickHoverEvent('plothover', e); + } + + function onClick(e) + { + triggerClickHoverEvent('plotclick', e); + } + + // trigger click or hover event (they send the same parameters so we share their code) + function triggerClickHoverEvent(eventname, e) + { + var offset = plot.offset(), + canvasX = parseInt(e.pageX - offset.left), + canvasY = parseInt(e.pageY - offset.top), + item = findNearbySlice(canvasX, canvasY); + + if (options.grid.autoHighlight) + { + // clear auto-highlights + for (var i = 0; i < highlights.length; ++i) + { + var h = highlights[i]; + if (h.auto == eventname && !(item && h.series == item.series)) + unhighlight(h.series); + } + } + + // highlight the slice + if (item) + highlight(item.series, eventname); + + // trigger any hover bind events + var pos = { pageX: e.pageX, pageY: e.pageY }; + target.trigger(eventname, [ pos, item ]); + } + + function highlight(s, auto) + { + if (typeof s == "number") + s = series[s]; + + var i = indexOfHighlight(s); + if (i == -1) + { + highlights.push({ series: s, auto: auto }); + plot.triggerRedrawOverlay(); + } + else if (!auto) + highlights[i].auto = false; + } + + function unhighlight(s) + { + if (s == null) + { + highlights = []; + plot.triggerRedrawOverlay(); + } + + if (typeof s == "number") + s = series[s]; + + var i = indexOfHighlight(s); + if (i != -1) + { + highlights.splice(i, 1); + plot.triggerRedrawOverlay(); + } + } + + function indexOfHighlight(s) + { + for (var i = 0; i < highlights.length; ++i) + { + var h = highlights[i]; + if (h.series == s) + return i; + } + return -1; + } + + function drawOverlay(plot, octx) + { + //alert(options.series.pie.radius); + var options = plot.getOptions(); + //alert(options.series.pie.radius); + + var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; + + octx.save(); + octx.translate(centerLeft, centerTop); + octx.scale(1, options.series.pie.tilt); + + for (i = 0; i < highlights.length; ++i) + drawHighlight(highlights[i].series); + + drawDonutHole(octx); + + octx.restore(); + + function drawHighlight(series) + { + if (series.angle < 0) return; + + //octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString(); + octx.fillStyle = "rgba(255, 255, 255, "+options.series.pie.highlight.opacity+")"; // this is temporary until we have access to parseColor + + octx.beginPath(); + if (Math.abs(series.angle - Math.PI*2) > 0.000000001) + octx.moveTo(0,0); // Center of the pie + octx.arc(0,0,radius,series.startAngle,series.startAngle+series.angle,false); + octx.closePath(); + octx.fill(); + } + + } + + } // end init (plugin body) + + // define pie specific options and their default values + var options = { + series: { + pie: { + show: false, + radius: 'auto', // actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value) + innerRadius:0, /* for donut */ + startAngle: 3/2, + tilt: 1, + offset: { + top: 0, + left: 'auto' + }, + stroke: { + color: '#FFF', + width: 1 + }, + label: { + show: 'auto', + formatter: function(label, slice){ + return '
'+label+'
'+Math.round(slice.percent)+'%
'; + }, // formatter function + radius: 1, // radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value) + background: { + color: null, + opacity: 0 + }, + threshold: 0 // percentage at which to hide the label (i.e. the slice is too narrow) + }, + combine: { + threshold: -1, // percentage at which to combine little slices into one larger slice + color: null, // color to give the new slice (auto-generated if null) + label: 'Other' // label to give the new slice + }, + highlight: { + //color: '#FFF', // will add this functionality once parseColor is available + opacity: 0.5 + } + } + } + }; + + $.plot.plugins.push({ + init: init, + options: options, + name: "pie", + version: "1.0" + }); +})(jQuery); diff --git a/cosmic-client/cosmic-ui/lib/flot/jquery.flot.resize.js b/cosmic-client/cosmic-ui/lib/flot/jquery.flot.resize.js new file mode 100644 index 0000000000..69dfb24f38 --- /dev/null +++ b/cosmic-client/cosmic-ui/lib/flot/jquery.flot.resize.js @@ -0,0 +1,60 @@ +/* +Flot plugin for automatically redrawing plots when the placeholder +size changes, e.g. on window resizes. + +It works by listening for changes on the placeholder div (through the +jQuery resize event plugin) - if the size changes, it will redraw the +plot. + +There are no options. If you need to disable the plugin for some +plots, you can just fix the size of their placeholders. +*/ + + +/* Inline dependency: + * jQuery resize event - v1.1 - 3/14/2010 + * http://benalman.com/projects/jquery-resize-plugin/ + * + * Copyright (c) 2010 "Cowboy" Ben Alman + * Dual licensed under the MIT and GPL licenses. + * http://benalman.com/about/license/ + */ +(function($,h,c){var a=$([]),e=$.resize=$.extend($.resize,{}),i,k="setTimeout",j="resize",d=j+"-special-event",b="delay",f="throttleWindow";e[b]=250;e[f]=true;$.event.special[j]={setup:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.add(l);$.data(this,d,{w:l.width(),h:l.height()});if(a.length===1){g()}},teardown:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.not(l);l.removeData(d);if(!a.length){clearTimeout(i)}},add:function(l){if(!e[f]&&this[k]){return false}var n;function m(s,o,p){var q=$(this),r=$.data(this,d);r.w=o!==c?o:q.width();r.h=p!==c?p:q.height();n.apply(this,arguments)}if($.isFunction(l)){n=l;return m}else{n=l.handler;l.handler=m}}};function g(){i=h[k](function(){a.each(function(){var n=$(this),m=n.width(),l=n.height(),o=$.data(this,d);if(m!==o.w||l!==o.h){n.trigger(j,[o.w=m,o.h=l])}});g()},e[b])}})(jQuery,this); + + +(function ($) { + var options = { }; // no options + + function init(plot) { + function onResize() { + var placeholder = plot.getPlaceholder(); + + // somebody might have hidden us and we can't plot + // when we don't have the dimensions + if (placeholder.width() == 0 || placeholder.height() == 0) + return; + + plot.resize(); + plot.setupGrid(); + plot.draw(); + } + + function bindEvents(plot, eventHolder) { + plot.getPlaceholder().resize(onResize); + } + + function shutdown(plot, eventHolder) { + plot.getPlaceholder().unbind("resize", onResize); + } + + plot.hooks.bindEvents.push(bindEvents); + plot.hooks.shutdown.push(shutdown); + } + + $.plot.plugins.push({ + init: init, + options: options, + name: 'resize', + version: '1.0' + }); +})(jQuery); diff --git a/cosmic-client/cosmic-ui/lib/flot/jquery.flot.selection.js b/cosmic-client/cosmic-ui/lib/flot/jquery.flot.selection.js new file mode 100644 index 0000000000..7f7b32694b --- /dev/null +++ b/cosmic-client/cosmic-ui/lib/flot/jquery.flot.selection.js @@ -0,0 +1,344 @@ +/* +Flot plugin for selecting regions. + +The plugin defines the following options: + + selection: { + mode: null or "x" or "y" or "xy", + color: color + } + +Selection support is enabled by setting the mode to one of "x", "y" or +"xy". In "x" mode, the user will only be able to specify the x range, +similarly for "y" mode. For "xy", the selection becomes a rectangle +where both ranges can be specified. "color" is color of the selection +(if you need to change the color later on, you can get to it with +plot.getOptions().selection.color). + +When selection support is enabled, a "plotselected" event will be +emitted on the DOM element you passed into the plot function. The +event handler gets a parameter with the ranges selected on the axes, +like this: + + placeholder.bind("plotselected", function(event, ranges) { + alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to) + // similar for yaxis - with multiple axes, the extra ones are in + // x2axis, x3axis, ... + }); + +The "plotselected" event is only fired when the user has finished +making the selection. A "plotselecting" event is fired during the +process with the same parameters as the "plotselected" event, in case +you want to know what's happening while it's happening, + +A "plotunselected" event with no arguments is emitted when the user +clicks the mouse to remove the selection. + +The plugin allso adds the following methods to the plot object: + +- setSelection(ranges, preventEvent) + + Set the selection rectangle. The passed in ranges is on the same + form as returned in the "plotselected" event. If the selection mode + is "x", you should put in either an xaxis range, if the mode is "y" + you need to put in an yaxis range and both xaxis and yaxis if the + selection mode is "xy", like this: + + setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } }); + + setSelection will trigger the "plotselected" event when called. If + you don't want that to happen, e.g. if you're inside a + "plotselected" handler, pass true as the second parameter. If you + are using multiple axes, you can specify the ranges on any of those, + e.g. as x2axis/x3axis/... instead of xaxis, the plugin picks the + first one it sees. + +- clearSelection(preventEvent) + + Clear the selection rectangle. Pass in true to avoid getting a + "plotunselected" event. + +- getSelection() + + Returns the current selection in the same format as the + "plotselected" event. If there's currently no selection, the + function returns null. + +*/ + +(function ($) { + function init(plot) { + var selection = { + first: { x: -1, y: -1}, second: { x: -1, y: -1}, + show: false, + active: false + }; + + // FIXME: The drag handling implemented here should be + // abstracted out, there's some similar code from a library in + // the navigation plugin, this should be massaged a bit to fit + // the Flot cases here better and reused. Doing this would + // make this plugin much slimmer. + var savedhandlers = {}; + + var mouseUpHandler = null; + + function onMouseMove(e) { + if (selection.active) { + updateSelection(e); + + plot.getPlaceholder().trigger("plotselecting", [ getSelection() ]); + } + } + + function onMouseDown(e) { + if (e.which != 1) // only accept left-click + return; + + // cancel out any text selections + document.body.focus(); + + // prevent text selection and drag in old-school browsers + if (document.onselectstart !== undefined && savedhandlers.onselectstart == null) { + savedhandlers.onselectstart = document.onselectstart; + document.onselectstart = function () { return false; }; + } + if (document.ondrag !== undefined && savedhandlers.ondrag == null) { + savedhandlers.ondrag = document.ondrag; + document.ondrag = function () { return false; }; + } + + setSelectionPos(selection.first, e); + + selection.active = true; + + // this is a bit silly, but we have to use a closure to be + // able to whack the same handler again + mouseUpHandler = function (e) { onMouseUp(e); }; + + $(document).one("mouseup", mouseUpHandler); + } + + function onMouseUp(e) { + mouseUpHandler = null; + + // revert drag stuff for old-school browsers + if (document.onselectstart !== undefined) + document.onselectstart = savedhandlers.onselectstart; + if (document.ondrag !== undefined) + document.ondrag = savedhandlers.ondrag; + + // no more dragging + selection.active = false; + updateSelection(e); + + if (selectionIsSane()) + triggerSelectedEvent(); + else { + // this counts as a clear + plot.getPlaceholder().trigger("plotunselected", [ ]); + plot.getPlaceholder().trigger("plotselecting", [ null ]); + } + + return false; + } + + function getSelection() { + if (!selectionIsSane()) + return null; + + var r = {}, c1 = selection.first, c2 = selection.second; + $.each(plot.getAxes(), function (name, axis) { + if (axis.used) { + var p1 = axis.c2p(c1[axis.direction]), p2 = axis.c2p(c2[axis.direction]); + r[name] = { from: Math.min(p1, p2), to: Math.max(p1, p2) }; + } + }); + return r; + } + + function triggerSelectedEvent() { + var r = getSelection(); + + plot.getPlaceholder().trigger("plotselected", [ r ]); + + // backwards-compat stuff, to be removed in future + if (r.xaxis && r.yaxis) + plot.getPlaceholder().trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]); + } + + function clamp(min, value, max) { + return value < min ? min: (value > max ? max: value); + } + + function setSelectionPos(pos, e) { + var o = plot.getOptions(); + var offset = plot.getPlaceholder().offset(); + var plotOffset = plot.getPlotOffset(); + pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width()); + pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plot.height()); + + if (o.selection.mode == "y") + pos.x = pos == selection.first ? 0 : plot.width(); + + if (o.selection.mode == "x") + pos.y = pos == selection.first ? 0 : plot.height(); + } + + function updateSelection(pos) { + if (pos.pageX == null) + return; + + setSelectionPos(selection.second, pos); + if (selectionIsSane()) { + selection.show = true; + plot.triggerRedrawOverlay(); + } + else + clearSelection(true); + } + + function clearSelection(preventEvent) { + if (selection.show) { + selection.show = false; + plot.triggerRedrawOverlay(); + if (!preventEvent) + plot.getPlaceholder().trigger("plotunselected", [ ]); + } + } + + // function taken from markings support in Flot + function extractRange(ranges, coord) { + var axis, from, to, key, axes = plot.getAxes(); + + for (var k in axes) { + axis = axes[k]; + if (axis.direction == coord) { + key = coord + axis.n + "axis"; + if (!ranges[key] && axis.n == 1) + key = coord + "axis"; // support x1axis as xaxis + if (ranges[key]) { + from = ranges[key].from; + to = ranges[key].to; + break; + } + } + } + + // backwards-compat stuff - to be removed in future + if (!ranges[key]) { + axis = coord == "x" ? plot.getXAxes()[0] : plot.getYAxes()[0]; + from = ranges[coord + "1"]; + to = ranges[coord + "2"]; + } + + // auto-reverse as an added bonus + if (from != null && to != null && from > to) { + var tmp = from; + from = to; + to = tmp; + } + + return { from: from, to: to, axis: axis }; + } + + function setSelection(ranges, preventEvent) { + var axis, range, o = plot.getOptions(); + + if (o.selection.mode == "y") { + selection.first.x = 0; + selection.second.x = plot.width(); + } + else { + range = extractRange(ranges, "x"); + + selection.first.x = range.axis.p2c(range.from); + selection.second.x = range.axis.p2c(range.to); + } + + if (o.selection.mode == "x") { + selection.first.y = 0; + selection.second.y = plot.height(); + } + else { + range = extractRange(ranges, "y"); + + selection.first.y = range.axis.p2c(range.from); + selection.second.y = range.axis.p2c(range.to); + } + + selection.show = true; + plot.triggerRedrawOverlay(); + if (!preventEvent && selectionIsSane()) + triggerSelectedEvent(); + } + + function selectionIsSane() { + var minSize = 5; + return Math.abs(selection.second.x - selection.first.x) >= minSize && + Math.abs(selection.second.y - selection.first.y) >= minSize; + } + + plot.clearSelection = clearSelection; + plot.setSelection = setSelection; + plot.getSelection = getSelection; + + plot.hooks.bindEvents.push(function(plot, eventHolder) { + var o = plot.getOptions(); + if (o.selection.mode != null) { + eventHolder.mousemove(onMouseMove); + eventHolder.mousedown(onMouseDown); + } + }); + + + plot.hooks.drawOverlay.push(function (plot, ctx) { + // draw selection + if (selection.show && selectionIsSane()) { + var plotOffset = plot.getPlotOffset(); + var o = plot.getOptions(); + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + var c = $.color.parse(o.selection.color); + + ctx.strokeStyle = c.scale('a', 0.8).toString(); + ctx.lineWidth = 1; + ctx.lineJoin = "round"; + ctx.fillStyle = c.scale('a', 0.4).toString(); + + var x = Math.min(selection.first.x, selection.second.x), + y = Math.min(selection.first.y, selection.second.y), + w = Math.abs(selection.second.x - selection.first.x), + h = Math.abs(selection.second.y - selection.first.y); + + ctx.fillRect(x, y, w, h); + ctx.strokeRect(x, y, w, h); + + ctx.restore(); + } + }); + + plot.hooks.shutdown.push(function (plot, eventHolder) { + eventHolder.unbind("mousemove", onMouseMove); + eventHolder.unbind("mousedown", onMouseDown); + + if (mouseUpHandler) + $(document).unbind("mouseup", mouseUpHandler); + }); + + } + + $.plot.plugins.push({ + init: init, + options: { + selection: { + mode: null, // one of null, "x", "y" or "xy" + color: "#e8cfac" + } + }, + name: 'selection', + version: '1.1' + }); +})(jQuery); diff --git a/cosmic-client/cosmic-ui/lib/flot/jquery.flot.stack.js b/cosmic-client/cosmic-ui/lib/flot/jquery.flot.stack.js new file mode 100644 index 0000000000..a31d5dc9b5 --- /dev/null +++ b/cosmic-client/cosmic-ui/lib/flot/jquery.flot.stack.js @@ -0,0 +1,184 @@ +/* +Flot plugin for stacking data sets, i.e. putting them on top of each +other, for accumulative graphs. + +The plugin assumes the data is sorted on x (or y if stacking +horizontally). For line charts, it is assumed that if a line has an +undefined gap (from a null point), then the line above it should have +the same gap - insert zeros instead of "null" if you want another +behaviour. This also holds for the start and end of the chart. Note +that stacking a mix of positive and negative values in most instances +doesn't make sense (so it looks weird). + +Two or more series are stacked when their "stack" attribute is set to +the same key (which can be any number or string or just "true"). To +specify the default stack, you can set + + series: { + stack: null or true or key (number/string) + } + +or specify it for a specific series + + $.plot($("#placeholder"), [{ data: [ ... ], stack: true }]) + +The stacking order is determined by the order of the data series in +the array (later series end up on top of the previous). + +Internally, the plugin modifies the datapoints in each series, adding +an offset to the y value. For line series, extra data points are +inserted through interpolation. If there's a second y value, it's also +adjusted (e.g for bar charts or filled areas). +*/ + +(function ($) { + var options = { + series: { stack: null } // or number/string + }; + + function init(plot) { + function findMatchingSeries(s, allseries) { + var res = null + for (var i = 0; i < allseries.length; ++i) { + if (s == allseries[i]) + break; + + if (allseries[i].stack == s.stack) + res = allseries[i]; + } + + return res; + } + + function stackData(plot, s, datapoints) { + if (s.stack == null) + return; + + var other = findMatchingSeries(s, plot.getData()); + if (!other) + return; + + var ps = datapoints.pointsize, + points = datapoints.points, + otherps = other.datapoints.pointsize, + otherpoints = other.datapoints.points, + newpoints = [], + px, py, intery, qx, qy, bottom, + withlines = s.lines.show, + horizontal = s.bars.horizontal, + withbottom = ps > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y), + withsteps = withlines && s.lines.steps, + fromgap = true, + keyOffset = horizontal ? 1 : 0, + accumulateOffset = horizontal ? 0 : 1, + i = 0, j = 0, l; + + while (true) { + if (i >= points.length) + break; + + l = newpoints.length; + + if (points[i] == null) { + // copy gaps + for (m = 0; m < ps; ++m) + newpoints.push(points[i + m]); + i += ps; + } + else if (j >= otherpoints.length) { + // for lines, we can't use the rest of the points + if (!withlines) { + for (m = 0; m < ps; ++m) + newpoints.push(points[i + m]); + } + i += ps; + } + else if (otherpoints[j] == null) { + // oops, got a gap + for (m = 0; m < ps; ++m) + newpoints.push(null); + fromgap = true; + j += otherps; + } + else { + // cases where we actually got two points + px = points[i + keyOffset]; + py = points[i + accumulateOffset]; + qx = otherpoints[j + keyOffset]; + qy = otherpoints[j + accumulateOffset]; + bottom = 0; + + if (px == qx) { + for (m = 0; m < ps; ++m) + newpoints.push(points[i + m]); + + newpoints[l + accumulateOffset] += qy; + bottom = qy; + + i += ps; + j += otherps; + } + else if (px > qx) { + // we got past point below, might need to + // insert interpolated extra point + if (withlines && i > 0 && points[i - ps] != null) { + intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px); + newpoints.push(qx); + newpoints.push(intery + qy); + for (m = 2; m < ps; ++m) + newpoints.push(points[i + m]); + bottom = qy; + } + + j += otherps; + } + else { // px < qx + if (fromgap && withlines) { + // if we come from a gap, we just skip this point + i += ps; + continue; + } + + for (m = 0; m < ps; ++m) + newpoints.push(points[i + m]); + + // we might be able to interpolate a point below, + // this can give us a better y + if (withlines && j > 0 && otherpoints[j - otherps] != null) + bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx); + + newpoints[l + accumulateOffset] += bottom; + + i += ps; + } + + fromgap = false; + + if (l != newpoints.length && withbottom) + newpoints[l + 2] += bottom; + } + + // maintain the line steps invariant + if (withsteps && l != newpoints.length && l > 0 + && newpoints[l] != null + && newpoints[l] != newpoints[l - ps] + && newpoints[l + 1] != newpoints[l - ps + 1]) { + for (m = 0; m < ps; ++m) + newpoints[l + ps + m] = newpoints[l + m]; + newpoints[l + 1] = newpoints[l - ps + 1]; + } + } + + datapoints.points = newpoints; + } + + plot.hooks.processDatapoints.push(stackData); + } + + $.plot.plugins.push({ + init: init, + options: options, + name: 'stack', + version: '1.2' + }); +})(jQuery); diff --git a/cosmic-client/cosmic-ui/lib/flot/jquery.flot.symbol.js b/cosmic-client/cosmic-ui/lib/flot/jquery.flot.symbol.js new file mode 100644 index 0000000000..a32fe3185b --- /dev/null +++ b/cosmic-client/cosmic-ui/lib/flot/jquery.flot.symbol.js @@ -0,0 +1,70 @@ +/* +Flot plugin that adds some extra symbols for plotting points. + +The symbols are accessed as strings through the standard symbol +choice: + + series: { + points: { + symbol: "square" // or "diamond", "triangle", "cross" + } + } + +*/ + +(function ($) { + function processRawData(plot, series, datapoints) { + // we normalize the area of each symbol so it is approximately the + // same as a circle of the given radius + + var handlers = { + square: function (ctx, x, y, radius, shadow) { + // pi * r^2 = (2s)^2 => s = r * sqrt(pi)/2 + var size = radius * Math.sqrt(Math.PI) / 2; + ctx.rect(x - size, y - size, size + size, size + size); + }, + diamond: function (ctx, x, y, radius, shadow) { + // pi * r^2 = 2s^2 => s = r * sqrt(pi/2) + var size = radius * Math.sqrt(Math.PI / 2); + ctx.moveTo(x - size, y); + ctx.lineTo(x, y - size); + ctx.lineTo(x + size, y); + ctx.lineTo(x, y + size); + ctx.lineTo(x - size, y); + }, + triangle: function (ctx, x, y, radius, shadow) { + // pi * r^2 = 1/2 * s^2 * sin (pi / 3) => s = r * sqrt(2 * pi / sin(pi / 3)) + var size = radius * Math.sqrt(2 * Math.PI / Math.sin(Math.PI / 3)); + var height = size * Math.sin(Math.PI / 3); + ctx.moveTo(x - size/2, y + height/2); + ctx.lineTo(x + size/2, y + height/2); + if (!shadow) { + ctx.lineTo(x, y - height/2); + ctx.lineTo(x - size/2, y + height/2); + } + }, + cross: function (ctx, x, y, radius, shadow) { + // pi * r^2 = (2s)^2 => s = r * sqrt(pi)/2 + var size = radius * Math.sqrt(Math.PI) / 2; + ctx.moveTo(x - size, y - size); + ctx.lineTo(x + size, y + size); + ctx.moveTo(x - size, y + size); + ctx.lineTo(x + size, y - size); + } + } + + var s = series.points.symbol; + if (handlers[s]) + series.points.symbol = handlers[s]; + } + + function init(plot) { + plot.hooks.processDatapoints.push(processRawData); + } + + $.plot.plugins.push({ + init: init, + name: 'symbols', + version: '1.0' + }); +})(jQuery); diff --git a/cosmic-client/cosmic-ui/lib/flot/jquery.flot.threshold.js b/cosmic-client/cosmic-ui/lib/flot/jquery.flot.threshold.js new file mode 100644 index 0000000000..0b2e7ac82a --- /dev/null +++ b/cosmic-client/cosmic-ui/lib/flot/jquery.flot.threshold.js @@ -0,0 +1,103 @@ +/* +Flot plugin for thresholding data. Controlled through the option +"threshold" in either the global series options + + series: { + threshold: { + below: number + color: colorspec + } + } + +or in a specific series + + $.plot($("#placeholder"), [{ data: [ ... ], threshold: { ... }}]) + +The data points below "below" are drawn with the specified color. This +makes it easy to mark points below 0, e.g. for budget data. + +Internally, the plugin works by splitting the data into two series, +above and below the threshold. The extra series below the threshold +will have its label cleared and the special "originSeries" attribute +set to the original series. You may need to check for this in hover +events. +*/ + +(function ($) { + var options = { + series: { threshold: null } // or { below: number, color: color spec} + }; + + function init(plot) { + function thresholdData(plot, s, datapoints) { + if (!s.threshold) + return; + + var ps = datapoints.pointsize, i, x, y, p, prevp, + thresholded = $.extend({}, s); // note: shallow copy + + thresholded.datapoints = { points: [], pointsize: ps }; + thresholded.label = null; + thresholded.color = s.threshold.color; + thresholded.threshold = null; + thresholded.originSeries = s; + thresholded.data = []; + + var below = s.threshold.below, + origpoints = datapoints.points, + addCrossingPoints = s.lines.show; + + threspoints = []; + newpoints = []; + + for (i = 0; i < origpoints.length; i += ps) { + x = origpoints[i] + y = origpoints[i + 1]; + + prevp = p; + if (y < below) + p = threspoints; + else + p = newpoints; + + if (addCrossingPoints && prevp != p && x != null + && i > 0 && origpoints[i - ps] != null) { + var interx = (x - origpoints[i - ps]) / (y - origpoints[i - ps + 1]) * (below - y) + x; + prevp.push(interx); + prevp.push(below); + for (m = 2; m < ps; ++m) + prevp.push(origpoints[i + m]); + + p.push(null); // start new segment + p.push(null); + for (m = 2; m < ps; ++m) + p.push(origpoints[i + m]); + p.push(interx); + p.push(below); + for (m = 2; m < ps; ++m) + p.push(origpoints[i + m]); + } + + p.push(x); + p.push(y); + } + + datapoints.points = newpoints; + thresholded.datapoints.points = threspoints; + + if (thresholded.datapoints.points.length > 0) + plot.getData().push(thresholded); + + // FIXME: there are probably some edge cases left in bars + } + + plot.hooks.processDatapoints.push(thresholdData); + } + + $.plot.plugins.push({ + init: init, + options: options, + name: 'threshold', + version: '1.0' + }); +})(jQuery); diff --git a/cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-bg_flat_0_aaaaaa_40x100.png b/cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-bg_flat_0_aaaaaa_40x100.png new file mode 100755 index 0000000000000000000000000000000000000000..5b5dab2ab7b1c50dea9cfe73dc5a269a92d2d4b4 GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F!3HG1q!d*FscKIb$B>N1x91EQ4=4yQ7#`R^ z$vje}bP0l+XkK DSH>_4 literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-bg_flat_75_ffffff_40x100.png b/cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-bg_flat_75_ffffff_40x100.png new file mode 100755 index 0000000000000000000000000000000000000000..ac8b229af950c29356abf64a6c4aa894575445f0 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F!3HG1q!d*FsY*{5$B>N1x91EQ4=4yQYz+E8 zPo9&<{J;c_6SHRil>2s{Zw^OT)6@jj2u|u!(plXsM>LJD`vD!n;OXk;vd$@?2>^GI BH@yG= literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-bg_glass_55_fbf9ee_1x400.png b/cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-bg_glass_55_fbf9ee_1x400.png new file mode 100755 index 0000000000000000000000000000000000000000..ad3d6346e00f246102f72f2e026ed0491988b394 GIT binary patch literal 120 zcmeAS@N?(olHy`uVBq!ia0vp^j6gJjgAK^akKnour0hLi978O6-<~(*I$*%ybaDOn z{W;e!B}_MSUQoPXhYd^Y6RUoS1yepnPx`2Kz)7OXQG!!=-jY=F+d2OOy?#DnJ32>z UEim$g7SJdLPgg&ebxsLQ09~*s;{X5v literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-bg_glass_65_ffffff_1x400.png b/cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-bg_glass_65_ffffff_1x400.png new file mode 100755 index 0000000000000000000000000000000000000000..42ccba269b6e91bef12ad0fa18be651b5ef0ee68 GIT binary patch literal 105 zcmeAS@N?(olHy`uVBq!ia0vp^j6gJjgAK^akKnouqzpV=978O6-=0?FV^9z|eBtf= z|7WztIJ;WT>{+tN>ySr~=F{k$>;_x^_y?afmf9pRKH0)6?eSP?3s5hEr>mdKI;Vst E0O;M1& literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-bg_glass_75_dadada_1x400.png b/cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-bg_glass_75_dadada_1x400.png new file mode 100755 index 0000000000000000000000000000000000000000..5a46b47cb16631068aee9e0bd61269fc4e95e5cd GIT binary patch literal 111 zcmeAS@N?(olHy`uVBq!ia0vp^j6gJjgAK^akKnouq|7{B978O6lPf+wIa#m9#>Unb zm^4K~wN3Zq+uP>V~E7myXQA@HYA8JKUDsG z{u#?PzmNO7dv`S|n8~xRJhV-o_rU%QLajY^Usjn;N@&CDX#zk literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-bg_inset-soft_95_fef1ec_1x100.png b/cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-bg_inset-soft_95_fef1ec_1x100.png new file mode 100755 index 0000000000000000000000000000000000000000..0e05810fffe0b6b8ac320e55d1eb4ba259b89d92 GIT binary patch literal 123 zcmeAS@N?(olHy`uVBq!ia0vp^j6j^i!3HGVb)pi0l#{26V~E7myXUR>S{Ou}E*`%9 zKPdOkfrN+ZlHSt7(uY{3{#;wiJb&Ugx1>W4qtrSDm(4hFaaY-$3p3x|sIU3`%J?Qj YcLn#R=pC)AfTl5cy85}Sb4q9e0MP_2(*OVf literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-icons_222222_256x240.png b/cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-icons_222222_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..b273ff111d219c9b9a8b96d57683d0075fb7871a GIT binary patch literal 4369 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 literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-icons_2e83ff_256x240.png b/cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-icons_2e83ff_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..09d1cdc856c292c4ab6dd818c7543ac0828bd616 GIT binary patch literal 4369 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 literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-icons_454545_256x240.png b/cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-icons_454545_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..59bd45b907c4fd965697774ce8c5fc6b2fd9c105 GIT binary patch literal 4369 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 oBo%DI*Kv;w;*%(i9W@f3_WCF#rGn literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-icons_cd0a0a_256x240.png b/cosmic-client/cosmic-ui/lib/jquery-ui/css/images/ui-icons_cd0a0a_256x240.png new file mode 100755 index 0000000000000000000000000000000000000000..2ab019b73ec11a485fa09378f3a0e155194f6a5d GIT binary patch literal 4369 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 + + + + jQuery UI Example Page + + + + + + + +

Welcome to jQuery UI!

+

This page demonstrates the widgets you downloaded using the theme you selected in the download builder. We've included and linked to minified versions of jQuery, your personalized copy of jQuery UI (js/jquery-ui-1.8.14.custom.min.js), and css/custom-theme/jquery-ui-1.8.14.custom.css which imports the entire jQuery UI CSS Framework. You can choose to link a subset of the CSS Framework depending on your needs.

+

You've downloaded components and a theme that are compatible with jQuery 1.3+. Please make sure you are using jQuery 1.3+ in your production environment.

+ +

YOUR COMPONENTS:

+ + +

Accordion

+
+
+

First

+
Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet.
+
+
+

Second

+
Phasellus mattis tincidunt nibh.
+
+
+

Third

+
Nam dui erat, auctor a, dignissim quis.
+
+
+ + +

Tabs

+
+ +
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
+
Phasellus mattis tincidunt nibh. Cras orci urna, blandit id, pretium vel, aliquet ornare, felis. Maecenas scelerisque sem non nisl. Fusce sed lorem in enim dictum bibendum.
+
Nam dui erat, auctor a, dignissim quis, sollicitudin eu, felis. Pellentesque nisi urna, interdum eget, sagittis et, consequat vestibulum, lacus. Mauris porttitor ullamcorper augue.
+
+ + +

Dialog

+

Open Dialog

+ + +

Overlay and Shadow Classes (not currently used in UI widgets)

+
+

Lorem ipsum dolor sit amet, Nulla nec tortor. Donec id elit quis purus consectetur consequat.

Nam congue semper tellus. Sed erat dolor, dapibus sit amet, venenatis ornare, ultrices ut, nisi. Aliquam ante. Suspendisse scelerisque dui nec velit. Duis augue augue, gravida euismod, vulputate ac, facilisis id, sem. Morbi in orci.

Nulla purus lacus, pulvinar vel, malesuada ac, mattis nec, quam. Nam molestie scelerisque quam. Nullam feugiat cursus lacus.orem ipsum dolor sit amet, consectetur adipiscing elit. Donec libero risus, commodo vitae, pharetra mollis, posuere eu, pede. Nulla nec tortor. Donec id elit quis purus consectetur consequat.

Nam congue semper tellus. Sed erat dolor, dapibus sit amet, venenatis ornare, ultrices ut, nisi. Aliquam ante. Suspendisse scelerisque dui nec velit. Duis augue augue, gravida euismod, vulputate ac, facilisis id, sem. Morbi in orci. Nulla purus lacus, pulvinar vel, malesuada ac, mattis nec, quam. Nam molestie scelerisque quam.

Nullam feugiat cursus lacus.orem ipsum dolor sit amet, consectetur adipiscing elit. Donec libero risus, commodo vitae, pharetra mollis, posuere eu, pede. Nulla nec tortor. Donec id elit quis purus consectetur consequat. Nam congue semper tellus. Sed erat dolor, dapibus sit amet, venenatis ornare, ultrices ut, nisi. Aliquam ante.

Suspendisse scelerisque dui nec velit. Duis augue augue, gravida euismod, vulputate ac, facilisis id, sem. Morbi in orci. Nulla purus lacus, pulvinar vel, malesuada ac, mattis nec, quam. Nam molestie scelerisque quam. Nullam feugiat cursus lacus.orem ipsum dolor sit amet, consectetur adipiscing elit. Donec libero risus, commodo vitae, pharetra mollis, posuere eu, pede. Nulla nec tortor. Donec id elit quis purus consectetur consequat. Nam congue semper tellus. Sed erat dolor, dapibus sit amet, venenatis ornare, ultrices ut, nisi.

+ + +
+
+
+

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

+
+
+ +
+ + + +
+

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

+
+ + + +

Framework Icons (content color preview)

+
    + +
  • +
  • +
  • + +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • + +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • + +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • + +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • + +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • + +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • + +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • + +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • + +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • + +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • + +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • + +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • + +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • + +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • + +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • + +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • + +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • + +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • + +
  • +
  • +
  • +
  • +
  • +
+ + + +

Slider

+
+ + +

Datepicker

+
+ + +

Progressbar

+
+ + +

Highlight / Error

+
+
+

+ Hey! Sample ui-state-highlight style.

+
+
+
+
+
+

+ Alert: Sample ui-state-error style.

+
+
+ + + + + diff --git a/cosmic-client/cosmic-ui/lib/jquery-ui/js/jquery-ui.js b/cosmic-client/cosmic-ui/lib/jquery-ui/js/jquery-ui.js new file mode 100755 index 0000000000..7ec85a5741 --- /dev/null +++ b/cosmic-client/cosmic-ui/lib/jquery-ui/js/jquery-ui.js @@ -0,0 +1,789 @@ +/*! + * jQuery UI 1.8.14 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI + */ +(function(c,j){function k(a,b){var d=a.nodeName.toLowerCase();if("area"===d){b=a.parentNode;d=b.name;if(!a.href||!d||b.nodeName.toLowerCase()!=="map")return false;a=c("img[usemap=#"+d+"]")[0];return!!a&&l(a)}return(/input|select|textarea|button|object/.test(d)?!a.disabled:"a"==d?a.href||b:b)&&l(a)}function l(a){return!c(a).parents().andSelf().filter(function(){return c.curCSS(this,"visibility")==="hidden"||c.expr.filters.hidden(this)}).length}c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.14", +keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});c.fn.extend({_focus:c.fn.focus,focus:function(a,b){return typeof a==="number"?this.each(function(){var d=this;setTimeout(function(){c(d).focus(); +b&&b.call(d)},a)}):this._focus.apply(this,arguments)},scrollParent:function(){var a;a=c.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(c.curCSS(this,"position",1))&&/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this, +"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!a.length?c(document):a},zIndex:function(a){if(a!==j)return this.css("zIndex",a);if(this.length){a=c(this[0]);for(var b;a.length&&a[0]!==document;){b=a.css("position");if(b==="absolute"||b==="relative"||b==="fixed"){b=parseInt(a.css("zIndex"),10);if(!isNaN(b)&&b!==0)return b}a=a.parent()}}return 0},disableSelection:function(){return this.bind((c.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection", +function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});c.each(["Width","Height"],function(a,b){function d(f,g,m,n){c.each(e,function(){g-=parseFloat(c.curCSS(f,"padding"+this,true))||0;if(m)g-=parseFloat(c.curCSS(f,"border"+this+"Width",true))||0;if(n)g-=parseFloat(c.curCSS(f,"margin"+this,true))||0});return g}var e=b==="Width"?["Left","Right"]:["Top","Bottom"],h=b.toLowerCase(),i={innerWidth:c.fn.innerWidth,innerHeight:c.fn.innerHeight,outerWidth:c.fn.outerWidth, +outerHeight:c.fn.outerHeight};c.fn["inner"+b]=function(f){if(f===j)return i["inner"+b].call(this);return this.each(function(){c(this).css(h,d(this,f)+"px")})};c.fn["outer"+b]=function(f,g){if(typeof f!=="number")return i["outer"+b].call(this,f);return this.each(function(){c(this).css(h,d(this,f,true,g)+"px")})}});c.extend(c.expr[":"],{data:function(a,b,d){return!!c.data(a,d[3])},focusable:function(a){return k(a,!isNaN(c.attr(a,"tabindex")))},tabbable:function(a){var b=c.attr(a,"tabindex"),d=isNaN(b); +return(d||b>=0)&&k(a,!d)}});c(function(){var a=document.body,b=a.appendChild(b=document.createElement("div"));c.extend(b.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});c.support.minHeight=b.offsetHeight===100;c.support.selectstart="onselectstart"in b;a.removeChild(b).style.display="none"});c.extend(c.ui,{plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&a.element[0].parentNode)for(var e= +0;e0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a=9)&&!a.button)return this._mouseUp(a);if(this._mouseStarted){this._mouseDrag(a);return a.preventDefault()}if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a))(this._mouseStarted=this._mouseStart(this._mouseDownEvent,a)!==false)?this._mouseDrag(a):this._mouseUp(a);return!this._mouseStarted},_mouseUp:function(a){b(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted= +false;a.target==this._mouseDownEvent.target&&b.data(a.target,this.widgetName+".preventClickEvent",true);this._mouseStop(a)}return false},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return true}})})(jQuery); +;/* + * jQuery UI Position 1.8.14 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Position + */ +(function(c){c.ui=c.ui||{};var n=/left|center|right/,o=/top|center|bottom/,t=c.fn.position,u=c.fn.offset;c.fn.position=function(b){if(!b||!b.of)return t.apply(this,arguments);b=c.extend({},b);var a=c(b.of),d=a[0],g=(b.collision||"flip").split(" "),e=b.offset?b.offset.split(" "):[0,0],h,k,j;if(d.nodeType===9){h=a.width();k=a.height();j={top:0,left:0}}else if(d.setTimeout){h=a.width();k=a.height();j={top:a.scrollTop(),left:a.scrollLeft()}}else if(d.preventDefault){b.at="left top";h=k=0;j={top:b.of.pageY, +left:b.of.pageX}}else{h=a.outerWidth();k=a.outerHeight();j=a.offset()}c.each(["my","at"],function(){var f=(b[this]||"").split(" ");if(f.length===1)f=n.test(f[0])?f.concat(["center"]):o.test(f[0])?["center"].concat(f):["center","center"];f[0]=n.test(f[0])?f[0]:"center";f[1]=o.test(f[1])?f[1]:"center";b[this]=f});if(g.length===1)g[1]=g[0];e[0]=parseInt(e[0],10)||0;if(e.length===1)e[1]=e[0];e[1]=parseInt(e[1],10)||0;if(b.at[0]==="right")j.left+=h;else if(b.at[0]==="center")j.left+=h/2;if(b.at[1]==="bottom")j.top+= +k;else if(b.at[1]==="center")j.top+=k/2;j.left+=e[0];j.top+=e[1];return this.each(function(){var f=c(this),l=f.outerWidth(),m=f.outerHeight(),p=parseInt(c.curCSS(this,"marginLeft",true))||0,q=parseInt(c.curCSS(this,"marginTop",true))||0,v=l+p+(parseInt(c.curCSS(this,"marginRight",true))||0),w=m+q+(parseInt(c.curCSS(this,"marginBottom",true))||0),i=c.extend({},j),r;if(b.my[0]==="right")i.left-=l;else if(b.my[0]==="center")i.left-=l/2;if(b.my[1]==="bottom")i.top-=m;else if(b.my[1]==="center")i.top-= +m/2;i.left=Math.round(i.left);i.top=Math.round(i.top);r={left:i.left-p,top:i.top-q};c.each(["left","top"],function(s,x){c.ui.position[g[s]]&&c.ui.position[g[s]][x](i,{targetWidth:h,targetHeight:k,elemWidth:l,elemHeight:m,collisionPosition:r,collisionWidth:v,collisionHeight:w,offset:e,my:b.my,at:b.at})});c.fn.bgiframe&&f.bgiframe();f.offset(c.extend(i,{using:b.using}))})};c.ui.position={fit:{left:function(b,a){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft();b.left= +d>0?b.left-d:Math.max(b.left-a.collisionPosition.left,b.left)},top:function(b,a){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();b.top=d>0?b.top-d:Math.max(b.top-a.collisionPosition.top,b.top)}},flip:{left:function(b,a){if(a.at[0]!=="center"){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft();var g=a.my[0]==="left"?-a.elemWidth:a.my[0]==="right"?a.elemWidth:0,e=a.at[0]==="left"?a.targetWidth:-a.targetWidth,h=-2*a.offset[0];b.left+= +a.collisionPosition.left<0?g+e+h:d>0?g+e+h:0}},top:function(b,a){if(a.at[1]!=="center"){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();var g=a.my[1]==="top"?-a.elemHeight:a.my[1]==="bottom"?a.elemHeight:0,e=a.at[1]==="top"?a.targetHeight:-a.targetHeight,h=-2*a.offset[1];b.top+=a.collisionPosition.top<0?g+e+h:d>0?g+e+h:0}}}};if(!c.offset.setOffset){c.offset.setOffset=function(b,a){if(/static/.test(c.curCSS(b,"position")))b.style.position="relative";var d=c(b), +g=d.offset(),e=parseInt(c.curCSS(b,"top",true),10)||0,h=parseInt(c.curCSS(b,"left",true),10)||0;g={top:a.top-g.top+e,left:a.left-g.left+h};"using"in a?a.using.call(b,g):d.css(g)};c.fn.offset=function(b){var a=this[0];if(!a||!a.ownerDocument)return null;if(b)return this.each(function(){c.offset.setOffset(this,b)});return u.call(this)}}})(jQuery); +;/* + * jQuery UI Draggable 1.8.14 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Draggables + * + * Depends: + * jquery.ui.core.js + * jquery.ui.mouse.js + * jquery.ui.widget.js + */ +(function(d){d.widget("ui.draggable",d.ui.mouse,{widgetEventPrefix:"drag",options:{addClasses:true,appendTo:"parent",axis:false,connectToSortable:false,containment:false,cursor:"auto",cursorAt:false,grid:false,handle:false,helper:"original",iframeFix:false,opacity:false,refreshPositions:false,revert:false,revertDuration:500,scope:"default",scroll:true,scrollSensitivity:20,scrollSpeed:20,snap:false,snapMode:"both",snapTolerance:20,stack:false,zIndex:false},_create:function(){if(this.options.helper== +"original"&&!/^(?:r|a|f)/.test(this.element.css("position")))this.element[0].style.position="relative";this.options.addClasses&&this.element.addClass("ui-draggable");this.options.disabled&&this.element.addClass("ui-draggable-disabled");this._mouseInit()},destroy:function(){if(this.element.data("draggable")){this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled");this._mouseDestroy();return this}},_mouseCapture:function(a){var b= +this.options;if(this.helper||b.disabled||d(a.target).is(".ui-resizable-handle"))return false;this.handle=this._getHandle(a);if(!this.handle)return false;d(b.iframeFix===true?"iframe":b.iframeFix).each(function(){d('
').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1E3}).css(d(this).offset()).appendTo("body")});return true},_mouseStart:function(a){var b=this.options;this.helper= +this._createHelper(a);this._cacheHelperProportions();if(d.ui.ddmanager)d.ui.ddmanager.current=this;this._cacheMargins();this.cssPosition=this.helper.css("position");this.scrollParent=this.helper.scrollParent();this.offset=this.positionAbs=this.element.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};d.extend(this.offset,{click:{left:a.pageX-this.offset.left,top:a.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}); +this.originalPosition=this.position=this._generatePosition(a);this.originalPageX=a.pageX;this.originalPageY=a.pageY;b.cursorAt&&this._adjustOffsetFromHelper(b.cursorAt);b.containment&&this._setContainment();if(this._trigger("start",a)===false){this._clear();return false}this._cacheHelperProportions();d.ui.ddmanager&&!b.dropBehaviour&&d.ui.ddmanager.prepareOffsets(this,a);this.helper.addClass("ui-draggable-dragging");this._mouseDrag(a,true);d.ui.ddmanager&&d.ui.ddmanager.dragStart(this,a);return true}, +_mouseDrag:function(a,b){this.position=this._generatePosition(a);this.positionAbs=this._convertPositionTo("absolute");if(!b){b=this._uiHash();if(this._trigger("drag",a,b)===false){this._mouseUp({});return false}this.position=b.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";d.ui.ddmanager&&d.ui.ddmanager.drag(this,a);return false},_mouseStop:function(a){var b= +false;if(d.ui.ddmanager&&!this.options.dropBehaviour)b=d.ui.ddmanager.drop(this,a);if(this.dropped){b=this.dropped;this.dropped=false}if((!this.element[0]||!this.element[0].parentNode)&&this.options.helper=="original")return false;if(this.options.revert=="invalid"&&!b||this.options.revert=="valid"&&b||this.options.revert===true||d.isFunction(this.options.revert)&&this.options.revert.call(this.element,b)){var c=this;d(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration, +10),function(){c._trigger("stop",a)!==false&&c._clear()})}else this._trigger("stop",a)!==false&&this._clear();return false},_mouseUp:function(a){this.options.iframeFix===true&&d("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)});d.ui.ddmanager&&d.ui.ddmanager.dragStop(this,a);return d.ui.mouse.prototype._mouseUp.call(this,a)},cancel:function(){this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear();return this},_getHandle:function(a){var b=!this.options.handle|| +!d(this.options.handle,this.element).length?true:false;d(this.options.handle,this.element).find("*").andSelf().each(function(){if(this==a.target)b=true});return b},_createHelper:function(a){var b=this.options;a=d.isFunction(b.helper)?d(b.helper.apply(this.element[0],[a])):b.helper=="clone"?this.element.clone().removeAttr("id"):this.element;a.parents("body").length||a.appendTo(b.appendTo=="parent"?this.element[0].parentNode:b.appendTo);a[0]!=this.element[0]&&!/(fixed|absolute)/.test(a.css("position"))&& +a.css("position","absolute");return a},_adjustOffsetFromHelper:function(a){if(typeof a=="string")a=a.split(" ");if(d.isArray(a))a={left:+a[0],top:+a[1]||0};if("left"in a)this.offset.click.left=a.left+this.margins.left;if("right"in a)this.offset.click.left=this.helperProportions.width-a.right+this.margins.left;if("top"in a)this.offset.click.top=a.top+this.margins.top;if("bottom"in a)this.offset.click.top=this.helperProportions.height-a.bottom+this.margins.top},_getParentOffset:function(){this.offsetParent= +this.helper.offsetParent();var a=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0])){a.left+=this.scrollParent.scrollLeft();a.top+=this.scrollParent.scrollTop()}if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&d.browser.msie)a={top:0,left:0};return{top:a.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:a.left+(parseInt(this.offsetParent.css("borderLeftWidth"), +10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a=this.element.position();return{top:a.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"), +10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var a=this.options;if(a.containment=="parent")a.containment=this.helper[0].parentNode;if(a.containment=="document"||a.containment=="window")this.containment=[a.containment=="document"?0:d(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,a.containment=="document"?0:d(window).scrollTop()-this.offset.relative.top-this.offset.parent.top, +(a.containment=="document"?0:d(window).scrollLeft())+d(a.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(a.containment=="document"?0:d(window).scrollTop())+(d(a.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(a.containment)&&a.containment.constructor!=Array){a=d(a.containment);var b=a[0];if(b){a.offset();var c=d(b).css("overflow")!= +"hidden";this.containment=[(parseInt(d(b).css("borderLeftWidth"),10)||0)+(parseInt(d(b).css("paddingLeft"),10)||0),(parseInt(d(b).css("borderTopWidth"),10)||0)+(parseInt(d(b).css("paddingTop"),10)||0),(c?Math.max(b.scrollWidth,b.offsetWidth):b.offsetWidth)-(parseInt(d(b).css("borderLeftWidth"),10)||0)-(parseInt(d(b).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(c?Math.max(b.scrollHeight,b.offsetHeight):b.offsetHeight)-(parseInt(d(b).css("borderTopWidth"), +10)||0)-(parseInt(d(b).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom];this.relative_container=a}}else if(a.containment.constructor==Array)this.containment=a.containment},_convertPositionTo:function(a,b){if(!b)b=this.position;a=a=="absolute"?1:-1;var c=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,f=/(html|body)/i.test(c[0].tagName);return{top:b.top+ +this.offset.relative.top*a+this.offset.parent.top*a-(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():f?0:c.scrollTop())*a),left:b.left+this.offset.relative.left*a+this.offset.parent.left*a-(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():f?0:c.scrollLeft())*a)}},_generatePosition:function(a){var b=this.options,c=this.cssPosition=="absolute"&& +!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,f=/(html|body)/i.test(c[0].tagName),e=a.pageX,h=a.pageY;if(this.originalPosition){var g;if(this.containment){if(this.relative_container){g=this.relative_container.offset();g=[this.containment[0]+g.left,this.containment[1]+g.top,this.containment[2]+g.left,this.containment[3]+g.top]}else g=this.containment;if(a.pageX-this.offset.click.leftg[2])e=g[2]+this.offset.click.left;if(a.pageY-this.offset.click.top>g[3])h=g[3]+this.offset.click.top}if(b.grid){h=b.grid[1]?this.originalPageY+Math.round((h-this.originalPageY)/b.grid[1])*b.grid[1]:this.originalPageY;h=g?!(h-this.offset.click.topg[3])?h:!(h-this.offset.click.topg[2])?e:!(e-this.offset.click.left=0;i--){var j=c.snapElements[i].left,l=j+c.snapElements[i].width,k=c.snapElements[i].top,m=k+c.snapElements[i].height;if(j-e=j&&f<=l||h>=j&&h<=l||fl)&&(e>= +i&&e<=k||g>=i&&g<=k||ek);default:return false}};d.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(a,b){var c=d.ui.ddmanager.droppables[a.options.scope]||[],e=b?b.type:null,g=(a.currentItem||a.element).find(":data(droppable)").andSelf(),f=0;a:for(;f').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(), +top:this.element.css("top"),left:this.element.css("left")}));this.element=this.element.parent().data("resizable",this.element.data("resizable"));this.elementIsWrapper=true;this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")});this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0});this.originalResizeStyle= +this.originalElement.css("resize");this.originalElement.css("resize","none");this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"}));this.originalElement.css({margin:this.originalElement.css("margin")});this._proportionallyResize()}this.handles=a.handles||(!e(".ui-resizable-handle",this.element).length?"e,s,se":{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne", +nw:".ui-resizable-nw"});if(this.handles.constructor==String){if(this.handles=="all")this.handles="n,e,s,w,se,sw,ne,nw";var c=this.handles.split(",");this.handles={};for(var d=0;d');/sw|se|ne|nw/.test(f)&&g.css({zIndex:++a.zIndex});"se"==f&&g.addClass("ui-icon ui-icon-gripsmall-diagonal-se");this.handles[f]=".ui-resizable-"+f;this.element.append(g)}}this._renderAxis=function(h){h=h||this.element;for(var i in this.handles){if(this.handles[i].constructor== +String)this.handles[i]=e(this.handles[i],this.element).show();if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var j=e(this.handles[i],this.element),l=0;l=/sw|ne|nw|se|n|s/.test(i)?j.outerHeight():j.outerWidth();j=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join("");h.css(j,l);this._proportionallyResize()}e(this.handles[i])}};this._renderAxis(this.element);this._handles=e(".ui-resizable-handle",this.element).disableSelection(); +this._handles.mouseover(function(){if(!b.resizing){if(this.className)var h=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);b.axis=h&&h[1]?h[1]:"se"}});if(a.autoHide){this._handles.hide();e(this.element).addClass("ui-resizable-autohide").hover(function(){if(!a.disabled){e(this).removeClass("ui-resizable-autohide");b._handles.show()}},function(){if(!a.disabled)if(!b.resizing){e(this).addClass("ui-resizable-autohide");b._handles.hide()}})}this._mouseInit()},destroy:function(){this._mouseDestroy(); +var b=function(c){e(c).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};if(this.elementIsWrapper){b(this.element);var a=this.element;a.after(this.originalElement.css({position:a.css("position"),width:a.outerWidth(),height:a.outerHeight(),top:a.css("top"),left:a.css("left")})).remove()}this.originalElement.css("resize",this.originalResizeStyle);b(this.originalElement);return this},_mouseCapture:function(b){var a= +false;for(var c in this.handles)if(e(this.handles[c])[0]==b.target)a=true;return!this.options.disabled&&a},_mouseStart:function(b){var a=this.options,c=this.element.position(),d=this.element;this.resizing=true;this.documentScroll={top:e(document).scrollTop(),left:e(document).scrollLeft()};if(d.is(".ui-draggable")||/absolute/.test(d.css("position")))d.css({position:"absolute",top:c.top,left:c.left});e.browser.opera&&/relative/.test(d.css("position"))&&d.css({position:"relative",top:"auto",left:"auto"}); +this._renderProxy();c=m(this.helper.css("left"));var f=m(this.helper.css("top"));if(a.containment){c+=e(a.containment).scrollLeft()||0;f+=e(a.containment).scrollTop()||0}this.offset=this.helper.offset();this.position={left:c,top:f};this.size=this._helper?{width:d.outerWidth(),height:d.outerHeight()}:{width:d.width(),height:d.height()};this.originalSize=this._helper?{width:d.outerWidth(),height:d.outerHeight()}:{width:d.width(),height:d.height()};this.originalPosition={left:c,top:f};this.sizeDiff= +{width:d.outerWidth()-d.width(),height:d.outerHeight()-d.height()};this.originalMousePosition={left:b.pageX,top:b.pageY};this.aspectRatio=typeof a.aspectRatio=="number"?a.aspectRatio:this.originalSize.width/this.originalSize.height||1;a=e(".ui-resizable-"+this.axis).css("cursor");e("body").css("cursor",a=="auto"?this.axis+"-resize":a);d.addClass("ui-resizable-resizing");this._propagate("start",b);return true},_mouseDrag:function(b){var a=this.helper,c=this.originalMousePosition,d=this._change[this.axis]; +if(!d)return false;c=d.apply(this,[b,b.pageX-c.left||0,b.pageY-c.top||0]);this._updateVirtualBoundaries(b.shiftKey);if(this._aspectRatio||b.shiftKey)c=this._updateRatio(c,b);c=this._respectSize(c,b);this._propagate("resize",b);a.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"});!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize();this._updateCache(c);this._trigger("resize",b,this.ui());return false}, +_mouseStop:function(b){this.resizing=false;var a=this.options,c=this;if(this._helper){var d=this._proportionallyResizeElements,f=d.length&&/textarea/i.test(d[0].nodeName);d=f&&e.ui.hasScroll(d[0],"left")?0:c.sizeDiff.height;f=f?0:c.sizeDiff.width;f={width:c.helper.width()-f,height:c.helper.height()-d};d=parseInt(c.element.css("left"),10)+(c.position.left-c.originalPosition.left)||null;var g=parseInt(c.element.css("top"),10)+(c.position.top-c.originalPosition.top)||null;a.animate||this.element.css(e.extend(f, +{top:g,left:d}));c.helper.height(c.size.height);c.helper.width(c.size.width);this._helper&&!a.animate&&this._proportionallyResize()}e("body").css("cursor","auto");this.element.removeClass("ui-resizable-resizing");this._propagate("stop",b);this._helper&&this.helper.remove();return false},_updateVirtualBoundaries:function(b){var a=this.options,c,d,f;a={minWidth:k(a.minWidth)?a.minWidth:0,maxWidth:k(a.maxWidth)?a.maxWidth:Infinity,minHeight:k(a.minHeight)?a.minHeight:0,maxHeight:k(a.maxHeight)?a.maxHeight: +Infinity};if(this._aspectRatio||b){b=a.minHeight*this.aspectRatio;d=a.minWidth/this.aspectRatio;c=a.maxHeight*this.aspectRatio;f=a.maxWidth/this.aspectRatio;if(b>a.minWidth)a.minWidth=b;if(d>a.minHeight)a.minHeight=d;if(cb.width,h=k(b.height)&&a.minHeight&&a.minHeight>b.height;if(g)b.width=a.minWidth;if(h)b.height=a.minHeight;if(d)b.width=a.maxWidth;if(f)b.height=a.maxHeight;var i=this.originalPosition.left+this.originalSize.width,j=this.position.top+this.size.height,l=/sw|nw|w/.test(c);c=/nw|ne|n/.test(c);if(g&&l)b.left=i-a.minWidth;if(d&&l)b.left=i-a.maxWidth;if(h&&c)b.top=j-a.minHeight;if(f&&c)b.top=j-a.maxHeight;if((a=!b.width&&!b.height)&&!b.left&&b.top)b.top=null;else if(a&&!b.top&&b.left)b.left= +null;return b},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var b=this.helper||this.element,a=0;a');var a=e.browser.msie&&e.browser.version<7,c=a?1:0;a=a?2:-1;this.helper.addClass(this._helper).css({width:this.element.outerWidth()+ +a,height:this.element.outerHeight()+a,position:"absolute",left:this.elementOffset.left-c+"px",top:this.elementOffset.top-c+"px",zIndex:++b.zIndex});this.helper.appendTo("body").disableSelection()}else this.helper=this.element},_change:{e:function(b,a){return{width:this.originalSize.width+a}},w:function(b,a){return{left:this.originalPosition.left+a,width:this.originalSize.width-a}},n:function(b,a,c){return{top:this.originalPosition.top+c,height:this.originalSize.height-c}},s:function(b,a,c){return{height:this.originalSize.height+ +c}},se:function(b,a,c){return e.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[b,a,c]))},sw:function(b,a,c){return e.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[b,a,c]))},ne:function(b,a,c){return e.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[b,a,c]))},nw:function(b,a,c){return e.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[b,a,c]))}},_propagate:function(b,a){e.ui.plugin.call(this,b,[a,this.ui()]); +b!="resize"&&this._trigger(b,a,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}});e.extend(e.ui.resizable,{version:"1.8.14"});e.ui.plugin.add("resizable","alsoResize",{start:function(){var b=e(this).data("resizable").options,a=function(c){e(c).each(function(){var d=e(this);d.data("resizable-alsoresize",{width:parseInt(d.width(), +10),height:parseInt(d.height(),10),left:parseInt(d.css("left"),10),top:parseInt(d.css("top"),10),position:d.css("position")})})};if(typeof b.alsoResize=="object"&&!b.alsoResize.parentNode)if(b.alsoResize.length){b.alsoResize=b.alsoResize[0];a(b.alsoResize)}else e.each(b.alsoResize,function(c){a(c)});else a(b.alsoResize)},resize:function(b,a){var c=e(this).data("resizable");b=c.options;var d=c.originalSize,f=c.originalPosition,g={height:c.size.height-d.height||0,width:c.size.width-d.width||0,top:c.position.top- +f.top||0,left:c.position.left-f.left||0},h=function(i,j){e(i).each(function(){var l=e(this),q=e(this).data("resizable-alsoresize"),p={},r=j&&j.length?j:l.parents(a.originalElement[0]).length?["width","height"]:["width","height","top","left"];e.each(r,function(n,o){if((n=(q[o]||0)+(g[o]||0))&&n>=0)p[o]=n||null});if(e.browser.opera&&/relative/.test(l.css("position"))){c._revertToRelativePosition=true;l.css({position:"absolute",top:"auto",left:"auto"})}l.css(p)})};typeof b.alsoResize=="object"&&!b.alsoResize.nodeType? +e.each(b.alsoResize,function(i,j){h(i,j)}):h(b.alsoResize)},stop:function(){var b=e(this).data("resizable"),a=b.options,c=function(d){e(d).each(function(){var f=e(this);f.css({position:f.data("resizable-alsoresize").position})})};if(b._revertToRelativePosition){b._revertToRelativePosition=false;typeof a.alsoResize=="object"&&!a.alsoResize.nodeType?e.each(a.alsoResize,function(d){c(d)}):c(a.alsoResize)}e(this).removeData("resizable-alsoresize")}});e.ui.plugin.add("resizable","animate",{stop:function(b){var a= +e(this).data("resizable"),c=a.options,d=a._proportionallyResizeElements,f=d.length&&/textarea/i.test(d[0].nodeName),g=f&&e.ui.hasScroll(d[0],"left")?0:a.sizeDiff.height;f={width:a.size.width-(f?0:a.sizeDiff.width),height:a.size.height-g};g=parseInt(a.element.css("left"),10)+(a.position.left-a.originalPosition.left)||null;var h=parseInt(a.element.css("top"),10)+(a.position.top-a.originalPosition.top)||null;a.element.animate(e.extend(f,h&&g?{top:h,left:g}:{}),{duration:c.animateDuration,easing:c.animateEasing, +step:function(){var i={width:parseInt(a.element.css("width"),10),height:parseInt(a.element.css("height"),10),top:parseInt(a.element.css("top"),10),left:parseInt(a.element.css("left"),10)};d&&d.length&&e(d[0]).css({width:i.width,height:i.height});a._updateCache(i);a._propagate("resize",b)}})}});e.ui.plugin.add("resizable","containment",{start:function(){var b=e(this).data("resizable"),a=b.element,c=b.options.containment;if(a=c instanceof e?c.get(0):/parent/.test(c)?a.parent().get(0):c){b.containerElement= +e(a);if(/document/.test(c)||c==document){b.containerOffset={left:0,top:0};b.containerPosition={left:0,top:0};b.parentData={element:e(document),left:0,top:0,width:e(document).width(),height:e(document).height()||document.body.parentNode.scrollHeight}}else{var d=e(a),f=[];e(["Top","Right","Left","Bottom"]).each(function(i,j){f[i]=m(d.css("padding"+j))});b.containerOffset=d.offset();b.containerPosition=d.position();b.containerSize={height:d.innerHeight()-f[3],width:d.innerWidth()-f[1]};c=b.containerOffset; +var g=b.containerSize.height,h=b.containerSize.width;h=e.ui.hasScroll(a,"left")?a.scrollWidth:h;g=e.ui.hasScroll(a)?a.scrollHeight:g;b.parentData={element:a,left:c.left,top:c.top,width:h,height:g}}}},resize:function(b){var a=e(this).data("resizable"),c=a.options,d=a.containerOffset,f=a.position;b=a._aspectRatio||b.shiftKey;var g={top:0,left:0},h=a.containerElement;if(h[0]!=document&&/static/.test(h.css("position")))g=d;if(f.left<(a._helper?d.left:0)){a.size.width+=a._helper?a.position.left-d.left: +a.position.left-g.left;if(b)a.size.height=a.size.width/c.aspectRatio;a.position.left=c.helper?d.left:0}if(f.top<(a._helper?d.top:0)){a.size.height+=a._helper?a.position.top-d.top:a.position.top;if(b)a.size.width=a.size.height*c.aspectRatio;a.position.top=a._helper?d.top:0}a.offset.left=a.parentData.left+a.position.left;a.offset.top=a.parentData.top+a.position.top;c=Math.abs((a._helper?a.offset.left-g.left:a.offset.left-g.left)+a.sizeDiff.width);d=Math.abs((a._helper?a.offset.top-g.top:a.offset.top- +d.top)+a.sizeDiff.height);f=a.containerElement.get(0)==a.element.parent().get(0);g=/relative|absolute/.test(a.containerElement.css("position"));if(f&&g)c-=a.parentData.left;if(c+a.size.width>=a.parentData.width){a.size.width=a.parentData.width-c;if(b)a.size.height=a.size.width/a.aspectRatio}if(d+a.size.height>=a.parentData.height){a.size.height=a.parentData.height-d;if(b)a.size.width=a.size.height*a.aspectRatio}},stop:function(){var b=e(this).data("resizable"),a=b.options,c=b.containerOffset,d=b.containerPosition, +f=b.containerElement,g=e(b.helper),h=g.offset(),i=g.outerWidth()-b.sizeDiff.width;g=g.outerHeight()-b.sizeDiff.height;b._helper&&!a.animate&&/relative/.test(f.css("position"))&&e(this).css({left:h.left-d.left-c.left,width:i,height:g});b._helper&&!a.animate&&/static/.test(f.css("position"))&&e(this).css({left:h.left-d.left-c.left,width:i,height:g})}});e.ui.plugin.add("resizable","ghost",{start:function(){var b=e(this).data("resizable"),a=b.options,c=b.size;b.ghost=b.originalElement.clone();b.ghost.css({opacity:0.25, +display:"block",position:"relative",height:c.height,width:c.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof a.ghost=="string"?a.ghost:"");b.ghost.appendTo(b.helper)},resize:function(){var b=e(this).data("resizable");b.ghost&&b.ghost.css({position:"relative",height:b.size.height,width:b.size.width})},stop:function(){var b=e(this).data("resizable");b.ghost&&b.helper&&b.helper.get(0).removeChild(b.ghost.get(0))}});e.ui.plugin.add("resizable","grid",{resize:function(){var b= +e(this).data("resizable"),a=b.options,c=b.size,d=b.originalSize,f=b.originalPosition,g=b.axis;a.grid=typeof a.grid=="number"?[a.grid,a.grid]:a.grid;var h=Math.round((c.width-d.width)/(a.grid[0]||1))*(a.grid[0]||1);a=Math.round((c.height-d.height)/(a.grid[1]||1))*(a.grid[1]||1);if(/^(se|s|e)$/.test(g)){b.size.width=d.width+h;b.size.height=d.height+a}else if(/^(ne)$/.test(g)){b.size.width=d.width+h;b.size.height=d.height+a;b.position.top=f.top-a}else{if(/^(sw)$/.test(g)){b.size.width=d.width+h;b.size.height= +d.height+a}else{b.size.width=d.width+h;b.size.height=d.height+a;b.position.top=f.top-a}b.position.left=f.left-h}}});var m=function(b){return parseInt(b,10)||0},k=function(b){return!isNaN(parseInt(b,10))}})(jQuery); +;/* + * jQuery UI Selectable 1.8.14 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Selectables + * + * Depends: + * jquery.ui.core.js + * jquery.ui.mouse.js + * jquery.ui.widget.js + */ +(function(e){e.widget("ui.selectable",e.ui.mouse,{options:{appendTo:"body",autoRefresh:true,distance:0,filter:"*",tolerance:"touch"},_create:function(){var c=this;this.element.addClass("ui-selectable");this.dragged=false;var f;this.refresh=function(){f=e(c.options.filter,c.element[0]);f.each(function(){var d=e(this),b=d.offset();e.data(this,"selectable-item",{element:this,$element:d,left:b.left,top:b.top,right:b.left+d.outerWidth(),bottom:b.top+d.outerHeight(),startselected:false,selected:d.hasClass("ui-selected"), +selecting:d.hasClass("ui-selecting"),unselecting:d.hasClass("ui-unselecting")})})};this.refresh();this.selectees=f.addClass("ui-selectee");this._mouseInit();this.helper=e("
")},destroy:function(){this.selectees.removeClass("ui-selectee").removeData("selectable-item");this.element.removeClass("ui-selectable ui-selectable-disabled").removeData("selectable").unbind(".selectable");this._mouseDestroy();return this},_mouseStart:function(c){var f=this;this.opos=[c.pageX, +c.pageY];if(!this.options.disabled){var d=this.options;this.selectees=e(d.filter,this.element[0]);this._trigger("start",c);e(d.appendTo).append(this.helper);this.helper.css({left:c.clientX,top:c.clientY,width:0,height:0});d.autoRefresh&&this.refresh();this.selectees.filter(".ui-selected").each(function(){var b=e.data(this,"selectable-item");b.startselected=true;if(!c.metaKey){b.$element.removeClass("ui-selected");b.selected=false;b.$element.addClass("ui-unselecting");b.unselecting=true;f._trigger("unselecting", +c,{unselecting:b.element})}});e(c.target).parents().andSelf().each(function(){var b=e.data(this,"selectable-item");if(b){var g=!c.metaKey||!b.$element.hasClass("ui-selected");b.$element.removeClass(g?"ui-unselecting":"ui-selected").addClass(g?"ui-selecting":"ui-unselecting");b.unselecting=!g;b.selecting=g;(b.selected=g)?f._trigger("selecting",c,{selecting:b.element}):f._trigger("unselecting",c,{unselecting:b.element});return false}})}},_mouseDrag:function(c){var f=this;this.dragged=true;if(!this.options.disabled){var d= +this.options,b=this.opos[0],g=this.opos[1],h=c.pageX,i=c.pageY;if(b>h){var j=h;h=b;b=j}if(g>i){j=i;i=g;g=j}this.helper.css({left:b,top:g,width:h-b,height:i-g});this.selectees.each(function(){var a=e.data(this,"selectable-item");if(!(!a||a.element==f.element[0])){var k=false;if(d.tolerance=="touch")k=!(a.left>h||a.righti||a.bottomb&&a.rightg&&a.bottom *",opacity:false,placeholder:false,revert:false,scroll:true,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1E3},_create:function(){var a=this.options;this.containerCache={};this.element.addClass("ui-sortable"); +this.refresh();this.floating=this.items.length?a.axis==="x"||/left|right/.test(this.items[0].item.css("float"))||/inline|table-cell/.test(this.items[0].item.css("display")):false;this.offset=this.element.offset();this._mouseInit()},destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled").removeData("sortable").unbind(".sortable");this._mouseDestroy();for(var a=this.items.length-1;a>=0;a--)this.items[a].item.removeData("sortable-item");return this},_setOption:function(a,b){if(a=== +"disabled"){this.options[a]=b;this.widget()[b?"addClass":"removeClass"]("ui-sortable-disabled")}else d.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(a,b){if(this.reverting)return false;if(this.options.disabled||this.options.type=="static")return false;this._refreshItems(a);var c=null,e=this;d(a.target).parents().each(function(){if(d.data(this,"sortable-item")==e){c=d(this);return false}});if(d.data(a.target,"sortable-item")==e)c=d(a.target);if(!c)return false;if(this.options.handle&& +!b){var f=false;d(this.options.handle,c).find("*").andSelf().each(function(){if(this==a.target)f=true});if(!f)return false}this.currentItem=c;this._removeCurrentsFromItems();return true},_mouseStart:function(a,b,c){b=this.options;var e=this;this.currentContainer=this;this.refreshPositions();this.helper=this._createHelper(a);this._cacheHelperProportions();this._cacheMargins();this.scrollParent=this.helper.scrollParent();this.offset=this.currentItem.offset();this.offset={top:this.offset.top-this.margins.top, +left:this.offset.left-this.margins.left};this.helper.css("position","absolute");this.cssPosition=this.helper.css("position");d.extend(this.offset,{click:{left:a.pageX-this.offset.left,top:a.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this._generatePosition(a);this.originalPageX=a.pageX;this.originalPageY=a.pageY;b.cursorAt&&this._adjustOffsetFromHelper(b.cursorAt);this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]}; +this.helper[0]!=this.currentItem[0]&&this.currentItem.hide();this._createPlaceholder();b.containment&&this._setContainment();if(b.cursor){if(d("body").css("cursor"))this._storedCursor=d("body").css("cursor");d("body").css("cursor",b.cursor)}if(b.opacity){if(this.helper.css("opacity"))this._storedOpacity=this.helper.css("opacity");this.helper.css("opacity",b.opacity)}if(b.zIndex){if(this.helper.css("zIndex"))this._storedZIndex=this.helper.css("zIndex");this.helper.css("zIndex",b.zIndex)}if(this.scrollParent[0]!= +document&&this.scrollParent[0].tagName!="HTML")this.overflowOffset=this.scrollParent.offset();this._trigger("start",a,this._uiHash());this._preserveHelperProportions||this._cacheHelperProportions();if(!c)for(c=this.containers.length-1;c>=0;c--)this.containers[c]._trigger("activate",a,e._uiHash(this));if(d.ui.ddmanager)d.ui.ddmanager.current=this;d.ui.ddmanager&&!b.dropBehaviour&&d.ui.ddmanager.prepareOffsets(this,a);this.dragging=true;this.helper.addClass("ui-sortable-helper");this._mouseDrag(a); +return true},_mouseDrag:function(a){this.position=this._generatePosition(a);this.positionAbs=this._convertPositionTo("absolute");if(!this.lastPositionAbs)this.lastPositionAbs=this.positionAbs;if(this.options.scroll){var b=this.options,c=false;if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"){if(this.overflowOffset.top+this.scrollParent[0].offsetHeight-a.pageY=0;b--){c=this.items[b];var e=c.item[0],f=this._intersectsWithPointer(c);if(f)if(e!=this.currentItem[0]&&this.placeholder[f==1?"next":"prev"]()[0]!=e&&!d.ui.contains(this.placeholder[0],e)&&(this.options.type=="semi-dynamic"?!d.ui.contains(this.element[0], +e):true)){this.direction=f==1?"down":"up";if(this.options.tolerance=="pointer"||this._intersectsWithSides(c))this._rearrange(a,c);else break;this._trigger("change",a,this._uiHash());break}}this._contactContainers(a);d.ui.ddmanager&&d.ui.ddmanager.drag(this,a);this._trigger("sort",a,this._uiHash());this.lastPositionAbs=this.positionAbs;return false},_mouseStop:function(a,b){if(a){d.ui.ddmanager&&!this.options.dropBehaviour&&d.ui.ddmanager.drop(this,a);if(this.options.revert){var c=this;b=c.placeholder.offset(); +c.reverting=true;d(this.helper).animate({left:b.left-this.offset.parent.left-c.margins.left+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollLeft),top:b.top-this.offset.parent.top-c.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){c._clear(a)})}else this._clear(a,b);return false}},cancel:function(){var a=this;if(this.dragging){this._mouseUp({target:null});this.options.helper=="original"?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"): +this.currentItem.show();for(var b=this.containers.length-1;b>=0;b--){this.containers[b]._trigger("deactivate",null,a._uiHash(this));if(this.containers[b].containerCache.over){this.containers[b]._trigger("out",null,a._uiHash(this));this.containers[b].containerCache.over=0}}}if(this.placeholder){this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]);this.options.helper!="original"&&this.helper&&this.helper[0].parentNode&&this.helper.remove();d.extend(this,{helper:null, +dragging:false,reverting:false,_noFinalSort:null});this.domPosition.prev?d(this.domPosition.prev).after(this.currentItem):d(this.domPosition.parent).prepend(this.currentItem)}return this},serialize:function(a){var b=this._getItemsAsjQuery(a&&a.connected),c=[];a=a||{};d(b).each(function(){var e=(d(a.item||this).attr(a.attribute||"id")||"").match(a.expression||/(.+)[-=_](.+)/);if(e)c.push((a.key||e[1]+"[]")+"="+(a.key&&a.expression?e[1]:e[2]))});!c.length&&a.key&&c.push(a.key+"=");return c.join("&")}, +toArray:function(a){var b=this._getItemsAsjQuery(a&&a.connected),c=[];a=a||{};b.each(function(){c.push(d(a.item||this).attr(a.attribute||"id")||"")});return c},_intersectsWith:function(a){var b=this.positionAbs.left,c=b+this.helperProportions.width,e=this.positionAbs.top,f=e+this.helperProportions.height,g=a.left,h=g+a.width,i=a.top,k=i+a.height,j=this.offset.click.top,l=this.offset.click.left;j=e+j>i&&e+jg&&b+la[this.floating?"width":"height"]?j:g0?"down":"up")},_getDragHorizontalDirection:function(){var a=this.positionAbs.left-this.lastPositionAbs.left;return a!=0&&(a>0?"right":"left")},refresh:function(a){this._refreshItems(a);this.refreshPositions();return this},_connectWith:function(){var a=this.options;return a.connectWith.constructor==String?[a.connectWith]:a.connectWith},_getItemsAsjQuery:function(a){var b=[],c=[],e=this._connectWith(); +if(e&&a)for(a=e.length-1;a>=0;a--)for(var f=d(e[a]),g=f.length-1;g>=0;g--){var h=d.data(f[g],"sortable");if(h&&h!=this&&!h.options.disabled)c.push([d.isFunction(h.options.items)?h.options.items.call(h.element):d(h.options.items,h.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),h])}c.push([d.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):d(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), +this]);for(a=c.length-1;a>=0;a--)c[a][0].each(function(){b.push(this)});return d(b)},_removeCurrentsFromItems:function(){for(var a=this.currentItem.find(":data(sortable-item)"),b=0;b=0;f--)for(var g=d(e[f]),h=g.length-1;h>=0;h--){var i=d.data(g[h],"sortable");if(i&&i!=this&&!i.options.disabled){c.push([d.isFunction(i.options.items)?i.options.items.call(i.element[0],a,{item:this.currentItem}):d(i.options.items,i.element),i]);this.containers.push(i)}}for(f=c.length-1;f>=0;f--){a=c[f][1];e=c[f][0];h=0;for(g=e.length;h=0;b--){var c=this.items[b];if(!(c.instance!=this.currentContainer&&this.currentContainer&&c.item[0]!=this.currentItem[0])){var e=this.options.toleranceElement?d(this.options.toleranceElement,c.item):c.item;if(!a){c.width=e.outerWidth();c.height=e.outerHeight()}e=e.offset();c.left=e.left;c.top=e.top}}if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(b= +this.containers.length-1;b>=0;b--){e=this.containers[b].element.offset();this.containers[b].containerCache.left=e.left;this.containers[b].containerCache.top=e.top;this.containers[b].containerCache.width=this.containers[b].element.outerWidth();this.containers[b].containerCache.height=this.containers[b].element.outerHeight()}return this},_createPlaceholder:function(a){var b=a||this,c=b.options;if(!c.placeholder||c.placeholder.constructor==String){var e=c.placeholder;c.placeholder={element:function(){var f= +d(document.createElement(b.currentItem[0].nodeName)).addClass(e||b.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];if(!e)f.style.visibility="hidden";return f},update:function(f,g){if(!(e&&!c.forcePlaceholderSize)){g.height()||g.height(b.currentItem.innerHeight()-parseInt(b.currentItem.css("paddingTop")||0,10)-parseInt(b.currentItem.css("paddingBottom")||0,10));g.width()||g.width(b.currentItem.innerWidth()-parseInt(b.currentItem.css("paddingLeft")||0,10)-parseInt(b.currentItem.css("paddingRight")|| +0,10))}}}}b.placeholder=d(c.placeholder.element.call(b.element,b.currentItem));b.currentItem.after(b.placeholder);c.placeholder.update(b,b.placeholder)},_contactContainers:function(a){for(var b=null,c=null,e=this.containers.length-1;e>=0;e--)if(!d.ui.contains(this.currentItem[0],this.containers[e].element[0]))if(this._intersectsWith(this.containers[e].containerCache)){if(!(b&&d.ui.contains(this.containers[e].element[0],b.element[0]))){b=this.containers[e];c=e}}else if(this.containers[e].containerCache.over){this.containers[e]._trigger("out", +a,this._uiHash(this));this.containers[e].containerCache.over=0}if(b)if(this.containers.length===1){this.containers[c]._trigger("over",a,this._uiHash(this));this.containers[c].containerCache.over=1}else if(this.currentContainer!=this.containers[c]){b=1E4;e=null;for(var f=this.positionAbs[this.containers[c].floating?"left":"top"],g=this.items.length-1;g>=0;g--)if(d.ui.contains(this.containers[c].element[0],this.items[g].item[0])){var h=this.items[g][this.containers[c].floating?"left":"top"];if(Math.abs(h- +f)this.containment[2])f=this.containment[2]+this.offset.click.left;if(a.pageY-this.offset.click.top>this.containment[3])g=this.containment[3]+this.offset.click.top}if(b.grid){g=this.originalPageY+Math.round((g- +this.originalPageY)/b.grid[1])*b.grid[1];g=this.containment?!(g-this.offset.click.topthis.containment[3])?g:!(g-this.offset.click.topthis.containment[2])?f:!(f-this.offset.click.left=0;e--)if(d.ui.contains(this.containers[e].element[0],this.currentItem[0])&&!b){c.push(function(f){return function(g){f._trigger("receive",g,this._uiHash(this))}}.call(this,this.containers[e]));c.push(function(f){return function(g){f._trigger("update",g,this._uiHash(this))}}.call(this,this.containers[e]))}}for(e=this.containers.length-1;e>=0;e--){b||c.push(function(f){return function(g){f._trigger("deactivate",g,this._uiHash(this))}}.call(this, +this.containers[e]));if(this.containers[e].containerCache.over){c.push(function(f){return function(g){f._trigger("out",g,this._uiHash(this))}}.call(this,this.containers[e]));this.containers[e].containerCache.over=0}}this._storedCursor&&d("body").css("cursor",this._storedCursor);this._storedOpacity&&this.helper.css("opacity",this._storedOpacity);if(this._storedZIndex)this.helper.css("zIndex",this._storedZIndex=="auto"?"":this._storedZIndex);this.dragging=false;if(this.cancelHelperRemoval){if(!b){this._trigger("beforeStop", +a,this._uiHash());for(e=0;e li > :first-child,> :not(li):even",icons:{header:"ui-icon-triangle-1-e",headerSelected:"ui-icon-triangle-1-s"},navigation:false,navigationFilter:function(){return this.href.toLowerCase()===location.href.toLowerCase()}},_create:function(){var a=this,b=a.options;a.running=0;a.element.addClass("ui-accordion ui-widget ui-helper-reset").children("li").addClass("ui-accordion-li-fix"); +a.headers=a.element.find(b.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all").bind("mouseenter.accordion",function(){b.disabled||c(this).addClass("ui-state-hover")}).bind("mouseleave.accordion",function(){b.disabled||c(this).removeClass("ui-state-hover")}).bind("focus.accordion",function(){b.disabled||c(this).addClass("ui-state-focus")}).bind("blur.accordion",function(){b.disabled||c(this).removeClass("ui-state-focus")});a.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom"); +if(b.navigation){var d=a.element.find("a").filter(b.navigationFilter).eq(0);if(d.length){var h=d.closest(".ui-accordion-header");a.active=h.length?h:d.closest(".ui-accordion-content").prev()}}a.active=a._findActive(a.active||b.active).addClass("ui-state-default ui-state-active").toggleClass("ui-corner-all").toggleClass("ui-corner-top");a.active.next().addClass("ui-accordion-content-active");a._createIcons();a.resize();a.element.attr("role","tablist");a.headers.attr("role","tab").bind("keydown.accordion", +function(f){return a._keydown(f)}).next().attr("role","tabpanel");a.headers.not(a.active||"").attr({"aria-expanded":"false","aria-selected":"false",tabIndex:-1}).next().hide();a.active.length?a.active.attr({"aria-expanded":"true","aria-selected":"true",tabIndex:0}):a.headers.eq(0).attr("tabIndex",0);c.browser.safari||a.headers.find("a").attr("tabIndex",-1);b.event&&a.headers.bind(b.event.split(" ").join(".accordion ")+".accordion",function(f){a._clickHandler.call(a,f,this);f.preventDefault()})},_createIcons:function(){var a= +this.options;if(a.icons){c("").addClass("ui-icon "+a.icons.header).prependTo(this.headers);this.active.children(".ui-icon").toggleClass(a.icons.header).toggleClass(a.icons.headerSelected);this.element.addClass("ui-accordion-icons")}},_destroyIcons:function(){this.headers.children(".ui-icon").remove();this.element.removeClass("ui-accordion-icons")},destroy:function(){var a=this.options;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role");this.headers.unbind(".accordion").removeClass("ui-accordion-header ui-accordion-disabled ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-selected").removeAttr("tabIndex"); +this.headers.find("a").removeAttr("tabIndex");this._destroyIcons();var b=this.headers.next().css("display","").removeAttr("role").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled");if(a.autoHeight||a.fillHeight)b.css("height","");return c.Widget.prototype.destroy.call(this)},_setOption:function(a,b){c.Widget.prototype._setOption.apply(this,arguments);a=="active"&&this.activate(b);if(a=="icons"){this._destroyIcons(); +b&&this._createIcons()}if(a=="disabled")this.headers.add(this.headers.next())[b?"addClass":"removeClass"]("ui-accordion-disabled ui-state-disabled")},_keydown:function(a){if(!(this.options.disabled||a.altKey||a.ctrlKey)){var b=c.ui.keyCode,d=this.headers.length,h=this.headers.index(a.target),f=false;switch(a.keyCode){case b.RIGHT:case b.DOWN:f=this.headers[(h+1)%d];break;case b.LEFT:case b.UP:f=this.headers[(h-1+d)%d];break;case b.SPACE:case b.ENTER:this._clickHandler({target:a.target},a.target); +a.preventDefault()}if(f){c(a.target).attr("tabIndex",-1);c(f).attr("tabIndex",0);f.focus();return false}return true}},resize:function(){var a=this.options,b;if(a.fillSpace){if(c.browser.msie){var d=this.element.parent().css("overflow");this.element.parent().css("overflow","hidden")}b=this.element.parent().height();c.browser.msie&&this.element.parent().css("overflow",d);this.headers.each(function(){b-=c(this).outerHeight(true)});this.headers.next().each(function(){c(this).height(Math.max(0,b-c(this).innerHeight()+ +c(this).height()))}).css("overflow","auto")}else if(a.autoHeight){b=0;this.headers.next().each(function(){b=Math.max(b,c(this).height("").height())}).height(b)}return this},activate:function(a){this.options.active=a;a=this._findActive(a)[0];this._clickHandler({target:a},a);return this},_findActive:function(a){return a?typeof a==="number"?this.headers.filter(":eq("+a+")"):this.headers.not(this.headers.not(a)):a===false?c([]):this.headers.filter(":eq(0)")},_clickHandler:function(a,b){var d=this.options; +if(!d.disabled)if(a.target){a=c(a.currentTarget||b);b=a[0]===this.active[0];d.active=d.collapsible&&b?false:this.headers.index(a);if(!(this.running||!d.collapsible&&b)){var h=this.active;j=a.next();g=this.active.next();e={options:d,newHeader:b&&d.collapsible?c([]):a,oldHeader:this.active,newContent:b&&d.collapsible?c([]):j,oldContent:g};var f=this.headers.index(this.active[0])>this.headers.index(a[0]);this.active=b?c([]):a;this._toggle(j,g,e,b,f);h.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header); +if(!b){a.removeClass("ui-state-default ui-corner-all").addClass("ui-state-active ui-corner-top").children(".ui-icon").removeClass(d.icons.header).addClass(d.icons.headerSelected);a.next().addClass("ui-accordion-content-active")}}}else if(d.collapsible){this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header);this.active.next().addClass("ui-accordion-content-active");var g=this.active.next(), +e={options:d,newHeader:c([]),oldHeader:d.active,newContent:c([]),oldContent:g},j=this.active=c([]);this._toggle(j,g,e)}},_toggle:function(a,b,d,h,f){var g=this,e=g.options;g.toShow=a;g.toHide=b;g.data=d;var j=function(){if(g)return g._completed.apply(g,arguments)};g._trigger("changestart",null,g.data);g.running=b.size()===0?a.size():b.size();if(e.animated){d={};d=e.collapsible&&h?{toShow:c([]),toHide:b,complete:j,down:f,autoHeight:e.autoHeight||e.fillSpace}:{toShow:a,toHide:b,complete:j,down:f,autoHeight:e.autoHeight|| +e.fillSpace};if(!e.proxied)e.proxied=e.animated;if(!e.proxiedDuration)e.proxiedDuration=e.duration;e.animated=c.isFunction(e.proxied)?e.proxied(d):e.proxied;e.duration=c.isFunction(e.proxiedDuration)?e.proxiedDuration(d):e.proxiedDuration;h=c.ui.accordion.animations;var i=e.duration,k=e.animated;if(k&&!h[k]&&!c.easing[k])k="slide";h[k]||(h[k]=function(l){this.slide(l,{easing:k,duration:i||700})});h[k](d)}else{if(e.collapsible&&h)a.toggle();else{b.hide();a.show()}j(true)}b.prev().attr({"aria-expanded":"false", +"aria-selected":"false",tabIndex:-1}).blur();a.prev().attr({"aria-expanded":"true","aria-selected":"true",tabIndex:0}).focus()},_completed:function(a){this.running=a?0:--this.running;if(!this.running){this.options.clearStyle&&this.toShow.add(this.toHide).css({height:"",overflow:""});this.toHide.removeClass("ui-accordion-content-active");if(this.toHide.length)this.toHide.parent()[0].className=this.toHide.parent()[0].className;this._trigger("change",null,this.data)}}});c.extend(c.ui.accordion,{version:"1.8.14", +animations:{slide:function(a,b){a=c.extend({easing:"swing",duration:300},a,b);if(a.toHide.size())if(a.toShow.size()){var d=a.toShow.css("overflow"),h=0,f={},g={},e;b=a.toShow;e=b[0].style.width;b.width(parseInt(b.parent().width(),10)-parseInt(b.css("paddingLeft"),10)-parseInt(b.css("paddingRight"),10)-(parseInt(b.css("borderLeftWidth"),10)||0)-(parseInt(b.css("borderRightWidth"),10)||0));c.each(["height","paddingTop","paddingBottom"],function(j,i){g[i]="hide";j=(""+c.css(a.toShow[0],i)).match(/^([\d+-.]+)(.*)$/); +f[i]={value:j[1],unit:j[2]||"px"}});a.toShow.css({height:0,overflow:"hidden"}).show();a.toHide.filter(":hidden").each(a.complete).end().filter(":visible").animate(g,{step:function(j,i){if(i.prop=="height")h=i.end-i.start===0?0:(i.now-i.start)/(i.end-i.start);a.toShow[0].style[i.prop]=h*f[i.prop].value+f[i.prop].unit},duration:a.duration,easing:a.easing,complete:function(){a.autoHeight||a.toShow.css("height","");a.toShow.css({width:e,overflow:d});a.complete()}})}else a.toHide.animate({height:"hide", +paddingTop:"hide",paddingBottom:"hide"},a);else a.toShow.animate({height:"show",paddingTop:"show",paddingBottom:"show"},a)},bounceslide:function(a){this.slide(a,{easing:a.down?"easeOutBounce":"swing",duration:a.down?1E3:200})}}})})(jQuery); +;/* + * jQuery UI Autocomplete 1.8.14 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Autocomplete + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + * jquery.ui.position.js + */ +(function(d){var e=0;d.widget("ui.autocomplete",{options:{appendTo:"body",autoFocus:false,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null},pending:0,_create:function(){var a=this,b=this.element[0].ownerDocument,g;this.element.addClass("ui-autocomplete-input").attr("autocomplete","off").attr({role:"textbox","aria-autocomplete":"list","aria-haspopup":"true"}).bind("keydown.autocomplete",function(c){if(!(a.options.disabled||a.element.attr("readonly"))){g= +false;var f=d.ui.keyCode;switch(c.keyCode){case f.PAGE_UP:a._move("previousPage",c);break;case f.PAGE_DOWN:a._move("nextPage",c);break;case f.UP:a._move("previous",c);c.preventDefault();break;case f.DOWN:a._move("next",c);c.preventDefault();break;case f.ENTER:case f.NUMPAD_ENTER:if(a.menu.active){g=true;c.preventDefault()}case f.TAB:if(!a.menu.active)return;a.menu.select(c);break;case f.ESCAPE:a.element.val(a.term);a.close(c);break;default:clearTimeout(a.searching);a.searching=setTimeout(function(){if(a.term!= +a.element.val()){a.selectedItem=null;a.search(null,c)}},a.options.delay);break}}}).bind("keypress.autocomplete",function(c){if(g){g=false;c.preventDefault()}}).bind("focus.autocomplete",function(){if(!a.options.disabled){a.selectedItem=null;a.previous=a.element.val()}}).bind("blur.autocomplete",function(c){if(!a.options.disabled){clearTimeout(a.searching);a.closing=setTimeout(function(){a.close(c);a._change(c)},150)}});this._initSource();this.response=function(){return a._response.apply(a,arguments)}; +this.menu=d("
    ").addClass("ui-autocomplete").appendTo(d(this.options.appendTo||"body",b)[0]).mousedown(function(c){var f=a.menu.element[0];d(c.target).closest(".ui-menu-item").length||setTimeout(function(){d(document).one("mousedown",function(h){h.target!==a.element[0]&&h.target!==f&&!d.ui.contains(f,h.target)&&a.close()})},1);setTimeout(function(){clearTimeout(a.closing)},13)}).menu({focus:function(c,f){f=f.item.data("item.autocomplete");false!==a._trigger("focus",c,{item:f})&&/^key/.test(c.originalEvent.type)&& +a.element.val(f.value)},selected:function(c,f){var h=f.item.data("item.autocomplete"),i=a.previous;if(a.element[0]!==b.activeElement){a.element.focus();a.previous=i;setTimeout(function(){a.previous=i;a.selectedItem=h},1)}false!==a._trigger("select",c,{item:h})&&a.element.val(h.value);a.term=a.element.val();a.close(c);a.selectedItem=h},blur:function(){a.menu.element.is(":visible")&&a.element.val()!==a.term&&a.element.val(a.term)}}).zIndex(this.element.zIndex()+1).css({top:0,left:0}).hide().data("menu"); +d.fn.bgiframe&&this.menu.element.bgiframe()},destroy:function(){this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete").removeAttr("role").removeAttr("aria-autocomplete").removeAttr("aria-haspopup");this.menu.element.remove();d.Widget.prototype.destroy.call(this)},_setOption:function(a,b){d.Widget.prototype._setOption.apply(this,arguments);a==="source"&&this._initSource();if(a==="appendTo")this.menu.element.appendTo(d(b||"body",this.element[0].ownerDocument)[0]);a==="disabled"&& +b&&this.xhr&&this.xhr.abort()},_initSource:function(){var a=this,b,g;if(d.isArray(this.options.source)){b=this.options.source;this.source=function(c,f){f(d.ui.autocomplete.filter(b,c.term))}}else if(typeof this.options.source==="string"){g=this.options.source;this.source=function(c,f){a.xhr&&a.xhr.abort();a.xhr=d.ajax({url:g,data:c,dataType:"json",autocompleteRequest:++e,success:function(h){this.autocompleteRequest===e&&f(h)},error:function(){this.autocompleteRequest===e&&f([])}})}}else this.source= +this.options.source},search:function(a,b){a=a!=null?a:this.element.val();this.term=this.element.val();if(a.length").data("item.autocomplete",b).append(d("").text(b.label)).appendTo(a)},_move:function(a,b){if(this.menu.element.is(":visible"))if(this.menu.first()&&/^previous/.test(a)||this.menu.last()&&/^next/.test(a)){this.element.val(this.term);this.menu.deactivate()}else this.menu[a](b);else this.search(null,b)},widget:function(){return this.menu.element}});d.extend(d.ui.autocomplete,{escapeRegex:function(a){return a.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, +"\\$&")},filter:function(a,b){var g=new RegExp(d.ui.autocomplete.escapeRegex(b),"i");return d.grep(a,function(c){return g.test(c.label||c.value||c)})}})})(jQuery); +(function(d){d.widget("ui.menu",{_create:function(){var e=this;this.element.addClass("ui-menu ui-widget ui-widget-content ui-corner-all").attr({role:"listbox","aria-activedescendant":"ui-active-menuitem"}).click(function(a){if(d(a.target).closest(".ui-menu-item a").length){a.preventDefault();e.select(a)}});this.refresh()},refresh:function(){var e=this;this.element.children("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","menuitem").children("a").addClass("ui-corner-all").attr("tabindex", +-1).mouseenter(function(a){e.activate(a,d(this).parent())}).mouseleave(function(){e.deactivate()})},activate:function(e,a){this.deactivate();if(this.hasScroll()){var b=a.offset().top-this.element.offset().top,g=this.element.scrollTop(),c=this.element.height();if(b<0)this.element.scrollTop(g+b);else b>=c&&this.element.scrollTop(g+b-c+a.height())}this.active=a.eq(0).children("a").addClass("ui-state-hover").attr("id","ui-active-menuitem").end();this._trigger("focus",e,{item:a})},deactivate:function(){if(this.active){this.active.children("a").removeClass("ui-state-hover").removeAttr("id"); +this._trigger("blur");this.active=null}},next:function(e){this.move("next",".ui-menu-item:first",e)},previous:function(e){this.move("prev",".ui-menu-item:last",e)},first:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},last:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},move:function(e,a,b){if(this.active){e=this.active[e+"All"](".ui-menu-item").eq(0);e.length?this.activate(b,e):this.activate(b,this.element.children(a))}else this.activate(b, +this.element.children(a))},nextPage:function(e){if(this.hasScroll())if(!this.active||this.last())this.activate(e,this.element.children(".ui-menu-item:first"));else{var a=this.active.offset().top,b=this.element.height(),g=this.element.children(".ui-menu-item").filter(function(){var c=d(this).offset().top-a-b+d(this).height();return c<10&&c>-10});g.length||(g=this.element.children(".ui-menu-item:last"));this.activate(e,g)}else this.activate(e,this.element.children(".ui-menu-item").filter(!this.active|| +this.last()?":first":":last"))},previousPage:function(e){if(this.hasScroll())if(!this.active||this.first())this.activate(e,this.element.children(".ui-menu-item:last"));else{var a=this.active.offset().top,b=this.element.height();result=this.element.children(".ui-menu-item").filter(function(){var g=d(this).offset().top-a+b-d(this).height();return g<10&&g>-10});result.length||(result=this.element.children(".ui-menu-item:first"));this.activate(e,result)}else this.activate(e,this.element.children(".ui-menu-item").filter(!this.active|| +this.first()?":last":":first"))},hasScroll:function(){return this.element.height()").addClass("ui-button-text").html(this.options.label).appendTo(a.empty()).text(),e=this.options.icons,f=e.primary&&e.secondary,d=[];if(e.primary||e.secondary){if(this.options.text)d.push("ui-button-text-icon"+(f?"s":e.primary?"-primary":"-secondary"));e.primary&&a.prepend("");e.secondary&&a.append("");if(!this.options.text){d.push(f?"ui-button-icons-only": +"ui-button-icon-only");this.hasTitle||a.attr("title",c)}}else d.push("ui-button-text-only");a.addClass(d.join(" "))}}});b.widget("ui.buttonset",{options:{items:":button, :submit, :reset, :checkbox, :radio, a, :data(button)"},_create:function(){this.element.addClass("ui-buttonset")},_init:function(){this.refresh()},_setOption:function(a,c){a==="disabled"&&this.buttons.button("option",a,c);b.Widget.prototype._setOption.apply(this,arguments)},refresh:function(){var a=this.element.css("direction")=== +"ltr";this.buttons=this.element.find(this.options.items).filter(":ui-button").button("refresh").end().not(":ui-button").button().end().map(function(){return b(this).button("widget")[0]}).removeClass("ui-corner-all ui-corner-left ui-corner-right").filter(":first").addClass(a?"ui-corner-left":"ui-corner-right").end().filter(":last").addClass(a?"ui-corner-right":"ui-corner-left").end().end()},destroy:function(){this.element.removeClass("ui-buttonset");this.buttons.map(function(){return b(this).button("widget")[0]}).removeClass("ui-corner-left ui-corner-right").end().button("destroy"); +b.Widget.prototype.destroy.call(this)}})})(jQuery); +;/* + * jQuery UI Dialog 1.8.14 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Dialog + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + * jquery.ui.button.js + * jquery.ui.draggable.js + * jquery.ui.mouse.js + * jquery.ui.position.js + * jquery.ui.resizable.js + */ +(function(c,l){var m={buttons:true,height:true,maxHeight:true,maxWidth:true,minHeight:true,minWidth:true,width:true},n={maxHeight:true,maxWidth:true,minHeight:true,minWidth:true},o=c.attrFn||{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true,click:true};c.widget("ui.dialog",{options:{autoOpen:true,buttons:{},closeOnEscape:true,closeText:"close",dialogClass:"",draggable:true,hide:null,height:"auto",maxHeight:false,maxWidth:false,minHeight:150,minWidth:150,modal:false, +position:{my:"center",at:"center",collision:"fit",using:function(a){var b=c(this).css(a).offset().top;b<0&&c(this).css("top",a.top-b)}},resizable:true,show:null,stack:true,title:"",width:300,zIndex:1E3},_create:function(){this.originalTitle=this.element.attr("title");if(typeof this.originalTitle!=="string")this.originalTitle="";this.options.title=this.options.title||this.originalTitle;var a=this,b=a.options,d=b.title||" ",e=c.ui.dialog.getTitleId(a.element),g=(a.uiDialog=c("
    ")).appendTo(document.body).hide().addClass("ui-dialog ui-widget ui-widget-content ui-corner-all "+ +b.dialogClass).css({zIndex:b.zIndex}).attr("tabIndex",-1).css("outline",0).keydown(function(i){if(b.closeOnEscape&&i.keyCode&&i.keyCode===c.ui.keyCode.ESCAPE){a.close(i);i.preventDefault()}}).attr({role:"dialog","aria-labelledby":e}).mousedown(function(i){a.moveToTop(false,i)});a.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(g);var f=(a.uiDialogTitlebar=c("
    ")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(g), +h=c('').addClass("ui-dialog-titlebar-close ui-corner-all").attr("role","button").hover(function(){h.addClass("ui-state-hover")},function(){h.removeClass("ui-state-hover")}).focus(function(){h.addClass("ui-state-focus")}).blur(function(){h.removeClass("ui-state-focus")}).click(function(i){a.close(i);return false}).appendTo(f);(a.uiDialogTitlebarCloseText=c("")).addClass("ui-icon ui-icon-closethick").text(b.closeText).appendTo(h);c("").addClass("ui-dialog-title").attr("id", +e).html(d).prependTo(f);if(c.isFunction(b.beforeclose)&&!c.isFunction(b.beforeClose))b.beforeClose=b.beforeclose;f.find("*").add(f).disableSelection();b.draggable&&c.fn.draggable&&a._makeDraggable();b.resizable&&c.fn.resizable&&a._makeResizable();a._createButtons(b.buttons);a._isOpen=false;c.fn.bgiframe&&g.bgiframe()},_init:function(){this.options.autoOpen&&this.open()},destroy:function(){var a=this;a.overlay&&a.overlay.destroy();a.uiDialog.hide();a.element.unbind(".dialog").removeData("dialog").removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body"); +a.uiDialog.remove();a.originalTitle&&a.element.attr("title",a.originalTitle);return a},widget:function(){return this.uiDialog},close:function(a){var b=this,d,e;if(false!==b._trigger("beforeClose",a)){b.overlay&&b.overlay.destroy();b.uiDialog.unbind("keypress.ui-dialog");b._isOpen=false;if(b.options.hide)b.uiDialog.hide(b.options.hide,function(){b._trigger("close",a)});else{b.uiDialog.hide();b._trigger("close",a)}c.ui.dialog.overlay.resize();if(b.options.modal){d=0;c(".ui-dialog").each(function(){if(this!== +b.uiDialog[0]){e=c(this).css("z-index");isNaN(e)||(d=Math.max(d,e))}});c.ui.dialog.maxZ=d}return b}},isOpen:function(){return this._isOpen},moveToTop:function(a,b){var d=this,e=d.options;if(e.modal&&!a||!e.stack&&!e.modal)return d._trigger("focus",b);if(e.zIndex>c.ui.dialog.maxZ)c.ui.dialog.maxZ=e.zIndex;if(d.overlay){c.ui.dialog.maxZ+=1;d.overlay.$el.css("z-index",c.ui.dialog.overlay.maxZ=c.ui.dialog.maxZ)}a={scrollTop:d.element.attr("scrollTop"),scrollLeft:d.element.attr("scrollLeft")};c.ui.dialog.maxZ+= +1;d.uiDialog.css("z-index",c.ui.dialog.maxZ);d.element.attr(a);d._trigger("focus",b);return d},open:function(){if(!this._isOpen){var a=this,b=a.options,d=a.uiDialog;a.overlay=b.modal?new c.ui.dialog.overlay(a):null;a._size();a._position(b.position);d.show(b.show);a.moveToTop(true);b.modal&&d.bind("keypress.ui-dialog",function(e){if(e.keyCode===c.ui.keyCode.TAB){var g=c(":tabbable",this),f=g.filter(":first");g=g.filter(":last");if(e.target===g[0]&&!e.shiftKey){f.focus(1);return false}else if(e.target=== +f[0]&&e.shiftKey){g.focus(1);return false}}});c(a.element.find(":tabbable").get().concat(d.find(".ui-dialog-buttonpane :tabbable").get().concat(d.get()))).eq(0).focus();a._isOpen=true;a._trigger("open");return a}},_createButtons:function(a){var b=this,d=false,e=c("
    ").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),g=c("
    ").addClass("ui-dialog-buttonset").appendTo(e);b.uiDialog.find(".ui-dialog-buttonpane").remove();typeof a==="object"&&a!==null&&c.each(a, +function(){return!(d=true)});if(d){c.each(a,function(f,h){h=c.isFunction(h)?{click:h,text:f}:h;var i=c('').click(function(){h.click.apply(b.element[0],arguments)}).appendTo(g);c.each(h,function(j,k){if(j!=="click")j in o?i[j](k):i.attr(j,k)});c.fn.button&&i.button()});e.appendTo(b.uiDialog)}},_makeDraggable:function(){function a(f){return{position:f.position,offset:f.offset}}var b=this,d=b.options,e=c(document),g;b.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close", +handle:".ui-dialog-titlebar",containment:"document",start:function(f,h){g=d.height==="auto"?"auto":c(this).height();c(this).height(c(this).height()).addClass("ui-dialog-dragging");b._trigger("dragStart",f,a(h))},drag:function(f,h){b._trigger("drag",f,a(h))},stop:function(f,h){d.position=[h.position.left-e.scrollLeft(),h.position.top-e.scrollTop()];c(this).removeClass("ui-dialog-dragging").height(g);b._trigger("dragStop",f,a(h));c.ui.dialog.overlay.resize()}})},_makeResizable:function(a){function b(f){return{originalPosition:f.originalPosition, +originalSize:f.originalSize,position:f.position,size:f.size}}a=a===l?this.options.resizable:a;var d=this,e=d.options,g=d.uiDialog.css("position");a=typeof a==="string"?a:"n,e,s,w,se,sw,ne,nw";d.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:d.element,maxWidth:e.maxWidth,maxHeight:e.maxHeight,minWidth:e.minWidth,minHeight:d._minHeight(),handles:a,start:function(f,h){c(this).addClass("ui-dialog-resizing");d._trigger("resizeStart",f,b(h))},resize:function(f,h){d._trigger("resize", +f,b(h))},stop:function(f,h){c(this).removeClass("ui-dialog-resizing");e.height=c(this).height();e.width=c(this).width();d._trigger("resizeStop",f,b(h));c.ui.dialog.overlay.resize()}}).css("position",g).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_minHeight:function(){var a=this.options;return a.height==="auto"?a.minHeight:Math.min(a.minHeight,a.height)},_position:function(a){var b=[],d=[0,0],e;if(a){if(typeof a==="string"||typeof a==="object"&&"0"in a){b=a.split?a.split(" "): +[a[0],a[1]];if(b.length===1)b[1]=b[0];c.each(["left","top"],function(g,f){if(+b[g]===b[g]){d[g]=b[g];b[g]=f}});a={my:b.join(" "),at:b.join(" "),offset:d.join(" ")}}a=c.extend({},c.ui.dialog.prototype.options.position,a)}else a=c.ui.dialog.prototype.options.position;(e=this.uiDialog.is(":visible"))||this.uiDialog.show();this.uiDialog.css({top:0,left:0}).position(c.extend({of:window},a));e||this.uiDialog.hide()},_setOptions:function(a){var b=this,d={},e=false;c.each(a,function(g,f){b._setOption(g,f); +if(g in m)e=true;if(g in n)d[g]=f});e&&this._size();this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option",d)},_setOption:function(a,b){var d=this,e=d.uiDialog;switch(a){case "beforeclose":a="beforeClose";break;case "buttons":d._createButtons(b);break;case "closeText":d.uiDialogTitlebarCloseText.text(""+b);break;case "dialogClass":e.removeClass(d.options.dialogClass).addClass("ui-dialog ui-widget ui-widget-content ui-corner-all "+b);break;case "disabled":b?e.addClass("ui-dialog-disabled"): +e.removeClass("ui-dialog-disabled");break;case "draggable":var g=e.is(":data(draggable)");g&&!b&&e.draggable("destroy");!g&&b&&d._makeDraggable();break;case "position":d._position(b);break;case "resizable":(g=e.is(":data(resizable)"))&&!b&&e.resizable("destroy");g&&typeof b==="string"&&e.resizable("option","handles",b);!g&&b!==false&&d._makeResizable(b);break;case "title":c(".ui-dialog-title",d.uiDialogTitlebar).html(""+(b||" "));break}c.Widget.prototype._setOption.apply(d,arguments)},_size:function(){var a= +this.options,b,d,e=this.uiDialog.is(":visible");this.element.show().css({width:"auto",minHeight:0,height:0});if(a.minWidth>a.width)a.width=a.minWidth;b=this.uiDialog.css({height:"auto",width:a.width}).height();d=Math.max(0,a.minHeight-b);if(a.height==="auto")if(c.support.minHeight)this.element.css({minHeight:d,height:"auto"});else{this.uiDialog.show();a=this.element.css("height","auto").height();e||this.uiDialog.hide();this.element.height(Math.max(a,d))}else this.element.height(Math.max(a.height- +b,0));this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())}});c.extend(c.ui.dialog,{version:"1.8.14",uuid:0,maxZ:0,getTitleId:function(a){a=a.attr("id");if(!a){this.uuid+=1;a=this.uuid}return"ui-dialog-title-"+a},overlay:function(a){this.$el=c.ui.dialog.overlay.create(a)}});c.extend(c.ui.dialog.overlay,{instances:[],oldInstances:[],maxZ:0,events:c.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),function(a){return a+".dialog-overlay"}).join(" "), +create:function(a){if(this.instances.length===0){setTimeout(function(){c.ui.dialog.overlay.instances.length&&c(document).bind(c.ui.dialog.overlay.events,function(d){if(c(d.target).zIndex()").addClass("ui-widget-overlay")).appendTo(document.body).css({width:this.width(), +height:this.height()});c.fn.bgiframe&&b.bgiframe();this.instances.push(b);return b},destroy:function(a){var b=c.inArray(a,this.instances);b!=-1&&this.oldInstances.push(this.instances.splice(b,1)[0]);this.instances.length===0&&c([document,window]).unbind(".dialog-overlay");a.remove();var d=0;c.each(this.instances,function(){d=Math.max(d,this.css("z-index"))});this.maxZ=d},height:function(){var a,b;if(c.browser.msie&&c.browser.version<7){a=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight); +b=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight);return a").appendTo(this.element).addClass("ui-slider-range ui-widget-header"+(a.range==="min"||a.range==="max"?" ui-slider-range-"+a.range:""))}for(var j=c.length;j"); +this.handles=c.add(d(e.join("")).appendTo(b.element));this.handle=this.handles.eq(0);this.handles.add(this.range).filter("a").click(function(g){g.preventDefault()}).hover(function(){a.disabled||d(this).addClass("ui-state-hover")},function(){d(this).removeClass("ui-state-hover")}).focus(function(){if(a.disabled)d(this).blur();else{d(".ui-slider .ui-state-focus").removeClass("ui-state-focus");d(this).addClass("ui-state-focus")}}).blur(function(){d(this).removeClass("ui-state-focus")});this.handles.each(function(g){d(this).data("index.ui-slider-handle", +g)});this.handles.keydown(function(g){var k=true,l=d(this).data("index.ui-slider-handle"),i,h,m;if(!b.options.disabled){switch(g.keyCode){case d.ui.keyCode.HOME:case d.ui.keyCode.END:case d.ui.keyCode.PAGE_UP:case d.ui.keyCode.PAGE_DOWN:case d.ui.keyCode.UP:case d.ui.keyCode.RIGHT:case d.ui.keyCode.DOWN:case d.ui.keyCode.LEFT:k=false;if(!b._keySliding){b._keySliding=true;d(this).addClass("ui-state-active");i=b._start(g,l);if(i===false)return}break}m=b.options.step;i=b.options.values&&b.options.values.length? +(h=b.values(l)):(h=b.value());switch(g.keyCode){case d.ui.keyCode.HOME:h=b._valueMin();break;case d.ui.keyCode.END:h=b._valueMax();break;case d.ui.keyCode.PAGE_UP:h=b._trimAlignValue(i+(b._valueMax()-b._valueMin())/5);break;case d.ui.keyCode.PAGE_DOWN:h=b._trimAlignValue(i-(b._valueMax()-b._valueMin())/5);break;case d.ui.keyCode.UP:case d.ui.keyCode.RIGHT:if(i===b._valueMax())return;h=b._trimAlignValue(i+m);break;case d.ui.keyCode.DOWN:case d.ui.keyCode.LEFT:if(i===b._valueMin())return;h=b._trimAlignValue(i- +m);break}b._slide(g,l,h);return k}}).keyup(function(g){var k=d(this).data("index.ui-slider-handle");if(b._keySliding){b._keySliding=false;b._stop(g,k);b._change(g,k);d(this).removeClass("ui-state-active")}});this._refreshValue();this._animateOff=false},destroy:function(){this.handles.remove();this.range.remove();this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-slider-disabled ui-widget ui-widget-content ui-corner-all").removeData("slider").unbind(".slider");this._mouseDestroy(); +return this},_mouseCapture:function(b){var a=this.options,c,f,e,j,g;if(a.disabled)return false;this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()};this.elementOffset=this.element.offset();c=this._normValueFromMouse({x:b.pageX,y:b.pageY});f=this._valueMax()-this._valueMin()+1;j=this;this.handles.each(function(k){var l=Math.abs(c-j.values(k));if(f>l){f=l;e=d(this);g=k}});if(a.range===true&&this.values(1)===a.min){g+=1;e=d(this.handles[g])}if(this._start(b,g)===false)return false; +this._mouseSliding=true;j._handleIndex=g;e.addClass("ui-state-active").focus();a=e.offset();this._clickOffset=!d(b.target).parents().andSelf().is(".ui-slider-handle")?{left:0,top:0}:{left:b.pageX-a.left-e.width()/2,top:b.pageY-a.top-e.height()/2-(parseInt(e.css("borderTopWidth"),10)||0)-(parseInt(e.css("borderBottomWidth"),10)||0)+(parseInt(e.css("marginTop"),10)||0)};this.handles.hasClass("ui-state-hover")||this._slide(b,g,c);return this._animateOff=true},_mouseStart:function(){return true},_mouseDrag:function(b){var a= +this._normValueFromMouse({x:b.pageX,y:b.pageY});this._slide(b,this._handleIndex,a);return false},_mouseStop:function(b){this.handles.removeClass("ui-state-active");this._mouseSliding=false;this._stop(b,this._handleIndex);this._change(b,this._handleIndex);this._clickOffset=this._handleIndex=null;return this._animateOff=false},_detectOrientation:function(){this.orientation=this.options.orientation==="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(b){var a;if(this.orientation==="horizontal"){a= +this.elementSize.width;b=b.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)}else{a=this.elementSize.height;b=b.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)}a=b/a;if(a>1)a=1;if(a<0)a=0;if(this.orientation==="vertical")a=1-a;b=this._valueMax()-this._valueMin();return this._trimAlignValue(this._valueMin()+a*b)},_start:function(b,a){var c={handle:this.handles[a],value:this.value()};if(this.options.values&&this.options.values.length){c.value=this.values(a); +c.values=this.values()}return this._trigger("start",b,c)},_slide:function(b,a,c){var f;if(this.options.values&&this.options.values.length){f=this.values(a?0:1);if(this.options.values.length===2&&this.options.range===true&&(a===0&&c>f||a===1&&c1){this.options.values[b]=this._trimAlignValue(a);this._refreshValue();this._change(null,b)}else if(arguments.length)if(d.isArray(arguments[0])){c=this.options.values;f=arguments[0];for(e=0;e=this._valueMax())return this._valueMax();var a=this.options.step>0?this.options.step:1,c=(b-this._valueMin())%a;alignValue=b-c;if(Math.abs(c)*2>=a)alignValue+=c>0?a:-a;return parseFloat(alignValue.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max}, +_refreshValue:function(){var b=this.options.range,a=this.options,c=this,f=!this._animateOff?a.animate:false,e,j={},g,k,l,i;if(this.options.values&&this.options.values.length)this.handles.each(function(h){e=(c.values(h)-c._valueMin())/(c._valueMax()-c._valueMin())*100;j[c.orientation==="horizontal"?"left":"bottom"]=e+"%";d(this).stop(1,1)[f?"animate":"css"](j,a.animate);if(c.options.range===true)if(c.orientation==="horizontal"){if(h===0)c.range.stop(1,1)[f?"animate":"css"]({left:e+"%"},a.animate); +if(h===1)c.range[f?"animate":"css"]({width:e-g+"%"},{queue:false,duration:a.animate})}else{if(h===0)c.range.stop(1,1)[f?"animate":"css"]({bottom:e+"%"},a.animate);if(h===1)c.range[f?"animate":"css"]({height:e-g+"%"},{queue:false,duration:a.animate})}g=e});else{k=this.value();l=this._valueMin();i=this._valueMax();e=i!==l?(k-l)/(i-l)*100:0;j[c.orientation==="horizontal"?"left":"bottom"]=e+"%";this.handle.stop(1,1)[f?"animate":"css"](j,a.animate);if(b==="min"&&this.orientation==="horizontal")this.range.stop(1, +1)[f?"animate":"css"]({width:e+"%"},a.animate);if(b==="max"&&this.orientation==="horizontal")this.range[f?"animate":"css"]({width:100-e+"%"},{queue:false,duration:a.animate});if(b==="min"&&this.orientation==="vertical")this.range.stop(1,1)[f?"animate":"css"]({height:e+"%"},a.animate);if(b==="max"&&this.orientation==="vertical")this.range[f?"animate":"css"]({height:100-e+"%"},{queue:false,duration:a.animate})}}});d.extend(d.ui.slider,{version:"1.8.14"})})(jQuery); +;/* + * jQuery UI Tabs 1.8.14 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Tabs + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + */ +(function(d,p){function u(){return++v}function w(){return++x}var v=0,x=0;d.widget("ui.tabs",{options:{add:null,ajaxOptions:null,cache:false,cookie:null,collapsible:false,disable:null,disabled:[],enable:null,event:"click",fx:null,idPrefix:"ui-tabs-",load:null,panelTemplate:"
    ",remove:null,select:null,show:null,spinner:"Loading…",tabTemplate:"
  • #{label}
  • "},_create:function(){this._tabify(true)},_setOption:function(b,e){if(b=="selected")this.options.collapsible&& +e==this.options.selected||this.select(e);else{this.options[b]=e;this._tabify()}},_tabId:function(b){return b.title&&b.title.replace(/\s/g,"_").replace(/[^\w\u00c0-\uFFFF-]/g,"")||this.options.idPrefix+u()},_sanitizeSelector:function(b){return b.replace(/:/g,"\\:")},_cookie:function(){var b=this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+w());return d.cookie.apply(null,[b].concat(d.makeArray(arguments)))},_ui:function(b,e){return{tab:b,panel:e,index:this.anchors.index(b)}},_cleanup:function(){this.lis.filter(".ui-state-processing").removeClass("ui-state-processing").find("span:data(label.tabs)").each(function(){var b= +d(this);b.html(b.data("label.tabs")).removeData("label.tabs")})},_tabify:function(b){function e(g,f){g.css("display","");!d.support.opacity&&f.opacity&&g[0].style.removeAttribute("filter")}var a=this,c=this.options,h=/^#.+/;this.list=this.element.find("ol,ul").eq(0);this.lis=d(" > li:has(a[href])",this.list);this.anchors=this.lis.map(function(){return d("a",this)[0]});this.panels=d([]);this.anchors.each(function(g,f){var i=d(f).attr("href"),l=i.split("#")[0],q;if(l&&(l===location.toString().split("#")[0]|| +(q=d("base")[0])&&l===q.href)){i=f.hash;f.href=i}if(h.test(i))a.panels=a.panels.add(a.element.find(a._sanitizeSelector(i)));else if(i&&i!=="#"){d.data(f,"href.tabs",i);d.data(f,"load.tabs",i.replace(/#.*$/,""));i=a._tabId(f);f.href="#"+i;f=a.element.find("#"+i);if(!f.length){f=d(c.panelTemplate).attr("id",i).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").insertAfter(a.panels[g-1]||a.list);f.data("destroy.tabs",true)}a.panels=a.panels.add(f)}else c.disabled.push(g)});if(b){this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all"); +this.list.addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.lis.addClass("ui-state-default ui-corner-top");this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom");if(c.selected===p){location.hash&&this.anchors.each(function(g,f){if(f.hash==location.hash){c.selected=g;return false}});if(typeof c.selected!=="number"&&c.cookie)c.selected=parseInt(a._cookie(),10);if(typeof c.selected!=="number"&&this.lis.filter(".ui-tabs-selected").length)c.selected= +this.lis.index(this.lis.filter(".ui-tabs-selected"));c.selected=c.selected||(this.lis.length?0:-1)}else if(c.selected===null)c.selected=-1;c.selected=c.selected>=0&&this.anchors[c.selected]||c.selected<0?c.selected:0;c.disabled=d.unique(c.disabled.concat(d.map(this.lis.filter(".ui-state-disabled"),function(g){return a.lis.index(g)}))).sort();d.inArray(c.selected,c.disabled)!=-1&&c.disabled.splice(d.inArray(c.selected,c.disabled),1);this.panels.addClass("ui-tabs-hide");this.lis.removeClass("ui-tabs-selected ui-state-active"); +if(c.selected>=0&&this.anchors.length){a.element.find(a._sanitizeSelector(a.anchors[c.selected].hash)).removeClass("ui-tabs-hide");this.lis.eq(c.selected).addClass("ui-tabs-selected ui-state-active");a.element.queue("tabs",function(){a._trigger("show",null,a._ui(a.anchors[c.selected],a.element.find(a._sanitizeSelector(a.anchors[c.selected].hash))[0]))});this.load(c.selected)}d(window).bind("unload",function(){a.lis.add(a.anchors).unbind(".tabs");a.lis=a.anchors=a.panels=null})}else c.selected=this.lis.index(this.lis.filter(".ui-tabs-selected")); +this.element[c.collapsible?"addClass":"removeClass"]("ui-tabs-collapsible");c.cookie&&this._cookie(c.selected,c.cookie);b=0;for(var j;j=this.lis[b];b++)d(j)[d.inArray(b,c.disabled)!=-1&&!d(j).hasClass("ui-tabs-selected")?"addClass":"removeClass"]("ui-state-disabled");c.cache===false&&this.anchors.removeData("cache.tabs");this.lis.add(this.anchors).unbind(".tabs");if(c.event!=="mouseover"){var k=function(g,f){f.is(":not(.ui-state-disabled)")&&f.addClass("ui-state-"+g)},n=function(g,f){f.removeClass("ui-state-"+ +g)};this.lis.bind("mouseover.tabs",function(){k("hover",d(this))});this.lis.bind("mouseout.tabs",function(){n("hover",d(this))});this.anchors.bind("focus.tabs",function(){k("focus",d(this).closest("li"))});this.anchors.bind("blur.tabs",function(){n("focus",d(this).closest("li"))})}var m,o;if(c.fx)if(d.isArray(c.fx)){m=c.fx[0];o=c.fx[1]}else m=o=c.fx;var r=o?function(g,f){d(g).closest("li").addClass("ui-tabs-selected ui-state-active");f.hide().removeClass("ui-tabs-hide").animate(o,o.duration||"normal", +function(){e(f,o);a._trigger("show",null,a._ui(g,f[0]))})}:function(g,f){d(g).closest("li").addClass("ui-tabs-selected ui-state-active");f.removeClass("ui-tabs-hide");a._trigger("show",null,a._ui(g,f[0]))},s=m?function(g,f){f.animate(m,m.duration||"normal",function(){a.lis.removeClass("ui-tabs-selected ui-state-active");f.addClass("ui-tabs-hide");e(f,m);a.element.dequeue("tabs")})}:function(g,f){a.lis.removeClass("ui-tabs-selected ui-state-active");f.addClass("ui-tabs-hide");a.element.dequeue("tabs")}; +this.anchors.bind(c.event+".tabs",function(){var g=this,f=d(g).closest("li"),i=a.panels.filter(":not(.ui-tabs-hide)"),l=a.element.find(a._sanitizeSelector(g.hash));if(f.hasClass("ui-tabs-selected")&&!c.collapsible||f.hasClass("ui-state-disabled")||f.hasClass("ui-state-processing")||a.panels.filter(":animated").length||a._trigger("select",null,a._ui(this,l[0]))===false){this.blur();return false}c.selected=a.anchors.index(this);a.abort();if(c.collapsible)if(f.hasClass("ui-tabs-selected")){c.selected= +-1;c.cookie&&a._cookie(c.selected,c.cookie);a.element.queue("tabs",function(){s(g,i)}).dequeue("tabs");this.blur();return false}else if(!i.length){c.cookie&&a._cookie(c.selected,c.cookie);a.element.queue("tabs",function(){r(g,l)});a.load(a.anchors.index(this));this.blur();return false}c.cookie&&a._cookie(c.selected,c.cookie);if(l.length){i.length&&a.element.queue("tabs",function(){s(g,i)});a.element.queue("tabs",function(){r(g,l)});a.load(a.anchors.index(this))}else throw"jQuery UI Tabs: Mismatching fragment identifier."; +d.browser.msie&&this.blur()});this.anchors.bind("click.tabs",function(){return false})},_getIndex:function(b){if(typeof b=="string")b=this.anchors.index(this.anchors.filter("[href$="+b+"]"));return b},destroy:function(){var b=this.options;this.abort();this.element.unbind(".tabs").removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible").removeData("tabs");this.list.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.anchors.each(function(){var e= +d.data(this,"href.tabs");if(e)this.href=e;var a=d(this).unbind(".tabs");d.each(["href","load","cache"],function(c,h){a.removeData(h+".tabs")})});this.lis.unbind(".tabs").add(this.panels).each(function(){d.data(this,"destroy.tabs")?d(this).remove():d(this).removeClass("ui-state-default ui-corner-top ui-tabs-selected ui-state-active ui-state-hover ui-state-focus ui-state-disabled ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide")});b.cookie&&this._cookie(null,b.cookie);return this},add:function(b, +e,a){if(a===p)a=this.anchors.length;var c=this,h=this.options;e=d(h.tabTemplate.replace(/#\{href\}/g,b).replace(/#\{label\}/g,e));b=!b.indexOf("#")?b.replace("#",""):this._tabId(d("a",e)[0]);e.addClass("ui-state-default ui-corner-top").data("destroy.tabs",true);var j=c.element.find("#"+b);j.length||(j=d(h.panelTemplate).attr("id",b).data("destroy.tabs",true));j.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide");if(a>=this.lis.length){e.appendTo(this.list);j.appendTo(this.list[0].parentNode)}else{e.insertBefore(this.lis[a]); +j.insertBefore(this.panels[a])}h.disabled=d.map(h.disabled,function(k){return k>=a?++k:k});this._tabify();if(this.anchors.length==1){h.selected=0;e.addClass("ui-tabs-selected ui-state-active");j.removeClass("ui-tabs-hide");this.element.queue("tabs",function(){c._trigger("show",null,c._ui(c.anchors[0],c.panels[0]))});this.load(0)}this._trigger("add",null,this._ui(this.anchors[a],this.panels[a]));return this},remove:function(b){b=this._getIndex(b);var e=this.options,a=this.lis.eq(b).remove(),c=this.panels.eq(b).remove(); +if(a.hasClass("ui-tabs-selected")&&this.anchors.length>1)this.select(b+(b+1=b?--h:h});this._tabify();this._trigger("remove",null,this._ui(a.find("a")[0],c[0]));return this},enable:function(b){b=this._getIndex(b);var e=this.options;if(d.inArray(b,e.disabled)!=-1){this.lis.eq(b).removeClass("ui-state-disabled");e.disabled=d.grep(e.disabled,function(a){return a!=b});this._trigger("enable",null, +this._ui(this.anchors[b],this.panels[b]));return this}},disable:function(b){b=this._getIndex(b);var e=this.options;if(b!=e.selected){this.lis.eq(b).addClass("ui-state-disabled");e.disabled.push(b);e.disabled.sort();this._trigger("disable",null,this._ui(this.anchors[b],this.panels[b]))}return this},select:function(b){b=this._getIndex(b);if(b==-1)if(this.options.collapsible&&this.options.selected!=-1)b=this.options.selected;else return this;this.anchors.eq(b).trigger(this.options.event+".tabs");return this}, +load:function(b){b=this._getIndex(b);var e=this,a=this.options,c=this.anchors.eq(b)[0],h=d.data(c,"load.tabs");this.abort();if(!h||this.element.queue("tabs").length!==0&&d.data(c,"cache.tabs"))this.element.dequeue("tabs");else{this.lis.eq(b).addClass("ui-state-processing");if(a.spinner){var j=d("span",c);j.data("label.tabs",j.html()).html(a.spinner)}this.xhr=d.ajax(d.extend({},a.ajaxOptions,{url:h,success:function(k,n){e.element.find(e._sanitizeSelector(c.hash)).html(k);e._cleanup();a.cache&&d.data(c, +"cache.tabs",true);e._trigger("load",null,e._ui(e.anchors[b],e.panels[b]));try{a.ajaxOptions.success(k,n)}catch(m){}},error:function(k,n){e._cleanup();e._trigger("load",null,e._ui(e.anchors[b],e.panels[b]));try{a.ajaxOptions.error(k,n,b,c)}catch(m){}}}));e.element.dequeue("tabs");return this}},abort:function(){this.element.queue([]);this.panels.stop(false,true);this.element.queue("tabs",this.element.queue("tabs").splice(-2,2));if(this.xhr){this.xhr.abort();delete this.xhr}this._cleanup();return this}, +url:function(b,e){this.anchors.eq(b).removeData("cache.tabs").data("load.tabs",e);return this},length:function(){return this.anchors.length}});d.extend(d.ui.tabs,{version:"1.8.14"});d.extend(d.ui.tabs.prototype,{rotation:null,rotate:function(b,e){var a=this,c=this.options,h=a._rotate||(a._rotate=function(j){clearTimeout(a.rotation);a.rotation=setTimeout(function(){var k=c.selected;a.select(++k'))}function N(a){return a.bind("mouseout",function(b){b= +d(b.target).closest("button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a");b.length&&b.removeClass("ui-state-hover ui-datepicker-prev-hover ui-datepicker-next-hover")}).bind("mouseover",function(b){b=d(b.target).closest("button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a");if(!(d.datepicker._isDisabledDatepicker(J.inline?a.parent()[0]:J.input[0])||!b.length)){b.parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover");b.addClass("ui-state-hover"); +b.hasClass("ui-datepicker-prev")&&b.addClass("ui-datepicker-prev-hover");b.hasClass("ui-datepicker-next")&&b.addClass("ui-datepicker-next-hover")}})}function H(a,b){d.extend(a,b);for(var c in b)if(b[c]==null||b[c]==C)a[c]=b[c];return a}d.extend(d.ui,{datepicker:{version:"1.8.14"}});var A=(new Date).getTime(),J;d.extend(M.prototype,{markerClassName:"hasDatepicker",maxRows:4,log:function(){this.debug&&console.log.apply("",arguments)},_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(a){H(this._defaults, +a||{});return this},_attachDatepicker:function(a,b){var c=null;for(var e in this._defaults){var f=a.getAttribute("date:"+e);if(f){c=c||{};try{c[e]=eval(f)}catch(h){c[e]=f}}}e=a.nodeName.toLowerCase();f=e=="div"||e=="span";if(!a.id){this.uuid+=1;a.id="dp"+this.uuid}var i=this._newInst(d(a),f);i.settings=d.extend({},b||{},c||{});if(e=="input")this._connectDatepicker(a,i);else f&&this._inlineDatepicker(a,i)},_newInst:function(a,b){return{id:a[0].id.replace(/([^A-Za-z0-9_-])/g,"\\\\$1"),input:a,selectedDay:0, +selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:b,dpDiv:!b?this.dpDiv:N(d('
    '))}},_connectDatepicker:function(a,b){var c=d(a);b.append=d([]);b.trigger=d([]);if(!c.hasClass(this.markerClassName)){this._attachments(c,b);c.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker",function(e,f,h){b.settings[f]= +h}).bind("getData.datepicker",function(e,f){return this._get(b,f)});this._autoSize(b);d.data(a,"datepicker",b)}},_attachments:function(a,b){var c=this._get(b,"appendText"),e=this._get(b,"isRTL");b.append&&b.append.remove();if(c){b.append=d(''+c+"");a[e?"before":"after"](b.append)}a.unbind("focus",this._showDatepicker);b.trigger&&b.trigger.remove();c=this._get(b,"showOn");if(c=="focus"||c=="both")a.focus(this._showDatepicker);if(c=="button"||c=="both"){c= +this._get(b,"buttonText");var f=this._get(b,"buttonImage");b.trigger=d(this._get(b,"buttonImageOnly")?d("").addClass(this._triggerClass).attr({src:f,alt:c,title:c}):d('').addClass(this._triggerClass).html(f==""?c:d("").attr({src:f,alt:c,title:c})));a[e?"before":"after"](b.trigger);b.trigger.click(function(){d.datepicker._datepickerShowing&&d.datepicker._lastInput==a[0]?d.datepicker._hideDatepicker():d.datepicker._showDatepicker(a[0]);return false})}},_autoSize:function(a){if(this._get(a, +"autoSize")&&!a.inline){var b=new Date(2009,11,20),c=this._get(a,"dateFormat");if(c.match(/[DM]/)){var e=function(f){for(var h=0,i=0,g=0;gh){h=f[g].length;i=g}return i};b.setMonth(e(this._get(a,c.match(/MM/)?"monthNames":"monthNamesShort")));b.setDate(e(this._get(a,c.match(/DD/)?"dayNames":"dayNamesShort"))+20-b.getDay())}a.input.attr("size",this._formatDate(a,b).length)}},_inlineDatepicker:function(a,b){var c=d(a);if(!c.hasClass(this.markerClassName)){c.addClass(this.markerClassName).append(b.dpDiv).bind("setData.datepicker", +function(e,f,h){b.settings[f]=h}).bind("getData.datepicker",function(e,f){return this._get(b,f)});d.data(a,"datepicker",b);this._setDate(b,this._getDefaultDate(b),true);this._updateDatepicker(b);this._updateAlternate(b);b.dpDiv.show()}},_dialogDatepicker:function(a,b,c,e,f){a=this._dialogInst;if(!a){this.uuid+=1;this._dialogInput=d('');this._dialogInput.keydown(this._doKeyDown);d("body").append(this._dialogInput); +a=this._dialogInst=this._newInst(this._dialogInput,false);a.settings={};d.data(this._dialogInput[0],"datepicker",a)}H(a.settings,e||{});b=b&&b.constructor==Date?this._formatDate(a,b):b;this._dialogInput.val(b);this._pos=f?f.length?f:[f.pageX,f.pageY]:null;if(!this._pos)this._pos=[document.documentElement.clientWidth/2-100+(document.documentElement.scrollLeft||document.body.scrollLeft),document.documentElement.clientHeight/2-150+(document.documentElement.scrollTop||document.body.scrollTop)];this._dialogInput.css("left", +this._pos[0]+20+"px").css("top",this._pos[1]+"px");a.settings.onSelect=c;this._inDialog=true;this.dpDiv.addClass(this._dialogClass);this._showDatepicker(this._dialogInput[0]);d.blockUI&&d.blockUI(this.dpDiv);d.data(this._dialogInput[0],"datepicker",a);return this},_destroyDatepicker:function(a){var b=d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();d.removeData(a,"datepicker");if(e=="input"){c.append.remove();c.trigger.remove();b.removeClass(this.markerClassName).unbind("focus", +this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)}else if(e=="div"||e=="span")b.removeClass(this.markerClassName).empty()}},_enableDatepicker:function(a){var b=d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();if(e=="input"){a.disabled=false;c.trigger.filter("button").each(function(){this.disabled=false}).end().filter("img").css({opacity:"1.0",cursor:""})}else if(e=="div"||e=="span"){b= +b.children("."+this._inlineClass);b.children().removeClass("ui-state-disabled");b.find("select.ui-datepicker-month, select.ui-datepicker-year").removeAttr("disabled")}this._disabledInputs=d.map(this._disabledInputs,function(f){return f==a?null:f})}},_disableDatepicker:function(a){var b=d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();if(e=="input"){a.disabled=true;c.trigger.filter("button").each(function(){this.disabled=true}).end().filter("img").css({opacity:"0.5", +cursor:"default"})}else if(e=="div"||e=="span"){b=b.children("."+this._inlineClass);b.children().addClass("ui-state-disabled");b.find("select.ui-datepicker-month, select.ui-datepicker-year").attr("disabled","disabled")}this._disabledInputs=d.map(this._disabledInputs,function(f){return f==a?null:f});this._disabledInputs[this._disabledInputs.length]=a}},_isDisabledDatepicker:function(a){if(!a)return false;for(var b=0;b-1}},_doKeyUp:function(a){a=d.datepicker._getInst(a.target);if(a.input.val()!=a.lastVal)try{if(d.datepicker.parseDate(d.datepicker._get(a,"dateFormat"),a.input?a.input.val():null,d.datepicker._getFormatConfig(a))){d.datepicker._setDateFromField(a); +d.datepicker._updateAlternate(a);d.datepicker._updateDatepicker(a)}}catch(b){d.datepicker.log(b)}return true},_showDatepicker:function(a){a=a.target||a;if(a.nodeName.toLowerCase()!="input")a=d("input",a.parentNode)[0];if(!(d.datepicker._isDisabledDatepicker(a)||d.datepicker._lastInput==a)){var b=d.datepicker._getInst(a);if(d.datepicker._curInst&&d.datepicker._curInst!=b){d.datepicker._datepickerShowing&&d.datepicker._triggerOnClose(d.datepicker._curInst);d.datepicker._curInst.dpDiv.stop(true,true)}var c= +d.datepicker._get(b,"beforeShow");H(b.settings,c?c.apply(a,[a,b]):{});b.lastVal=null;d.datepicker._lastInput=a;d.datepicker._setDateFromField(b);if(d.datepicker._inDialog)a.value="";if(!d.datepicker._pos){d.datepicker._pos=d.datepicker._findPos(a);d.datepicker._pos[1]+=a.offsetHeight}var e=false;d(a).parents().each(function(){e|=d(this).css("position")=="fixed";return!e});if(e&&d.browser.opera){d.datepicker._pos[0]-=document.documentElement.scrollLeft;d.datepicker._pos[1]-=document.documentElement.scrollTop}c= +{left:d.datepicker._pos[0],top:d.datepicker._pos[1]};d.datepicker._pos=null;b.dpDiv.empty();b.dpDiv.css({position:"absolute",display:"block",top:"-1000px"});d.datepicker._updateDatepicker(b);c=d.datepicker._checkOffset(b,c,e);b.dpDiv.css({position:d.datepicker._inDialog&&d.blockUI?"static":e?"fixed":"absolute",display:"none",left:c.left+"px",top:c.top+"px"});if(!b.inline){c=d.datepicker._get(b,"showAnim");var f=d.datepicker._get(b,"duration"),h=function(){var i=b.dpDiv.find("iframe.ui-datepicker-cover"); +if(i.length){var g=d.datepicker._getBorders(b.dpDiv);i.css({left:-g[0],top:-g[1],width:b.dpDiv.outerWidth(),height:b.dpDiv.outerHeight()})}};b.dpDiv.zIndex(d(a).zIndex()+1);d.datepicker._datepickerShowing=true;d.effects&&d.effects[c]?b.dpDiv.show(c,d.datepicker._get(b,"showOptions"),f,h):b.dpDiv[c||"show"](c?f:null,h);if(!c||!f)h();b.input.is(":visible")&&!b.input.is(":disabled")&&b.input.focus();d.datepicker._curInst=b}}},_updateDatepicker:function(a){this.maxRows=4;var b=d.datepicker._getBorders(a.dpDiv); +J=a;a.dpDiv.empty().append(this._generateHTML(a));var c=a.dpDiv.find("iframe.ui-datepicker-cover");c.length&&c.css({left:-b[0],top:-b[1],width:a.dpDiv.outerWidth(),height:a.dpDiv.outerHeight()});a.dpDiv.find("."+this._dayOverClass+" a").mouseover();b=this._getNumberOfMonths(a);c=b[1];a.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width("");c>1&&a.dpDiv.addClass("ui-datepicker-multi-"+c).css("width",17*c+"em");a.dpDiv[(b[0]!=1||b[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi"); +a.dpDiv[(this._get(a,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl");a==d.datepicker._curInst&&d.datepicker._datepickerShowing&&a.input&&a.input.is(":visible")&&!a.input.is(":disabled")&&a.input[0]!=document.activeElement&&a.input.focus();if(a.yearshtml){var e=a.yearshtml;setTimeout(function(){e===a.yearshtml&&a.yearshtml&&a.dpDiv.find("select.ui-datepicker-year:first").replaceWith(a.yearshtml);e=a.yearshtml=null},0)}},_getBorders:function(a){var b=function(c){return{thin:1,medium:2,thick:3}[c]|| +c};return[parseFloat(b(a.css("border-left-width"))),parseFloat(b(a.css("border-top-width")))]},_checkOffset:function(a,b,c){var e=a.dpDiv.outerWidth(),f=a.dpDiv.outerHeight(),h=a.input?a.input.outerWidth():0,i=a.input?a.input.outerHeight():0,g=document.documentElement.clientWidth+d(document).scrollLeft(),j=document.documentElement.clientHeight+d(document).scrollTop();b.left-=this._get(a,"isRTL")?e-h:0;b.left-=c&&b.left==a.input.offset().left?d(document).scrollLeft():0;b.top-=c&&b.top==a.input.offset().top+ +i?d(document).scrollTop():0;b.left-=Math.min(b.left,b.left+e>g&&g>e?Math.abs(b.left+e-g):0);b.top-=Math.min(b.top,b.top+f>j&&j>f?Math.abs(f+i):0);return b},_findPos:function(a){for(var b=this._get(this._getInst(a),"isRTL");a&&(a.type=="hidden"||a.nodeType!=1||d.expr.filters.hidden(a));)a=a[b?"previousSibling":"nextSibling"];a=d(a).offset();return[a.left,a.top]},_triggerOnClose:function(a){var b=this._get(a,"onClose");if(b)b.apply(a.input?a.input[0]:null,[a.input?a.input.val():"",a])},_hideDatepicker:function(a){var b= +this._curInst;if(!(!b||a&&b!=d.data(a,"datepicker")))if(this._datepickerShowing){a=this._get(b,"showAnim");var c=this._get(b,"duration"),e=function(){d.datepicker._tidyDialog(b);this._curInst=null};d.effects&&d.effects[a]?b.dpDiv.hide(a,d.datepicker._get(b,"showOptions"),c,e):b.dpDiv[a=="slideDown"?"slideUp":a=="fadeIn"?"fadeOut":"hide"](a?c:null,e);a||e();d.datepicker._triggerOnClose(b);this._datepickerShowing=false;this._lastInput=null;if(this._inDialog){this._dialogInput.css({position:"absolute", +left:"0",top:"-100px"});if(d.blockUI){d.unblockUI();d("body").append(this.dpDiv)}}this._inDialog=false}},_tidyDialog:function(a){a.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(a){if(d.datepicker._curInst){a=d(a.target);a[0].id!=d.datepicker._mainDivId&&a.parents("#"+d.datepicker._mainDivId).length==0&&!a.hasClass(d.datepicker.markerClassName)&&!a.hasClass(d.datepicker._triggerClass)&&d.datepicker._datepickerShowing&&!(d.datepicker._inDialog&& +d.blockUI)&&d.datepicker._hideDatepicker()}},_adjustDate:function(a,b,c){a=d(a);var e=this._getInst(a[0]);if(!this._isDisabledDatepicker(a[0])){this._adjustInstDate(e,b+(c=="M"?this._get(e,"showCurrentAtPos"):0),c);this._updateDatepicker(e)}},_gotoToday:function(a){a=d(a);var b=this._getInst(a[0]);if(this._get(b,"gotoCurrent")&&b.currentDay){b.selectedDay=b.currentDay;b.drawMonth=b.selectedMonth=b.currentMonth;b.drawYear=b.selectedYear=b.currentYear}else{var c=new Date;b.selectedDay=c.getDate();b.drawMonth= +b.selectedMonth=c.getMonth();b.drawYear=b.selectedYear=c.getFullYear()}this._notifyChange(b);this._adjustDate(a)},_selectMonthYear:function(a,b,c){a=d(a);var e=this._getInst(a[0]);e._selectingMonthYear=false;e["selected"+(c=="M"?"Month":"Year")]=e["draw"+(c=="M"?"Month":"Year")]=parseInt(b.options[b.selectedIndex].value,10);this._notifyChange(e);this._adjustDate(a)},_clickMonthYear:function(a){var b=this._getInst(d(a)[0]);b.input&&b._selectingMonthYear&&setTimeout(function(){b.input.focus()},0);b._selectingMonthYear= +!b._selectingMonthYear},_selectDay:function(a,b,c,e){var f=d(a);if(!(d(e).hasClass(this._unselectableClass)||this._isDisabledDatepicker(f[0]))){f=this._getInst(f[0]);f.selectedDay=f.currentDay=d("a",e).html();f.selectedMonth=f.currentMonth=b;f.selectedYear=f.currentYear=c;this._selectDate(a,this._formatDate(f,f.currentDay,f.currentMonth,f.currentYear))}},_clearDate:function(a){a=d(a);this._getInst(a[0]);this._selectDate(a,"")},_selectDate:function(a,b){a=this._getInst(d(a)[0]);b=b!=null?b:this._formatDate(a); +a.input&&a.input.val(b);this._updateAlternate(a);var c=this._get(a,"onSelect");if(c)c.apply(a.input?a.input[0]:null,[b,a]);else a.input&&a.input.trigger("change");if(a.inline)this._updateDatepicker(a);else{this._hideDatepicker();this._lastInput=a.input[0];typeof a.input[0]!="object"&&a.input.focus();this._lastInput=null}},_updateAlternate:function(a){var b=this._get(a,"altField");if(b){var c=this._get(a,"altFormat")||this._get(a,"dateFormat"),e=this._getDate(a),f=this.formatDate(c,e,this._getFormatConfig(a)); +d(b).each(function(){d(this).val(f)})}},noWeekends:function(a){a=a.getDay();return[a>0&&a<6,""]},iso8601Week:function(a){a=new Date(a.getTime());a.setDate(a.getDate()+4-(a.getDay()||7));var b=a.getTime();a.setMonth(0);a.setDate(1);return Math.floor(Math.round((b-a)/864E5)/7)+1},parseDate:function(a,b,c){if(a==null||b==null)throw"Invalid arguments";b=typeof b=="object"?b.toString():b+"";if(b=="")return null;var e=(c?c.shortYearCutoff:null)||this._defaults.shortYearCutoff;e=typeof e!="string"?e:(new Date).getFullYear()% +100+parseInt(e,10);for(var f=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,h=(c?c.dayNames:null)||this._defaults.dayNames,i=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,g=(c?c.monthNames:null)||this._defaults.monthNames,j=c=-1,l=-1,u=-1,k=false,o=function(p){(p=B+1-1){j=1;l=u;do{e=this._getDaysInMonth(c,j-1);if(l<=e)break;j++;l-=e}while(1)}v=this._daylightSavingAdjust(new Date(c,j-1,l));if(v.getFullYear()!=c||v.getMonth()+1!=j||v.getDate()!=l)throw"Invalid date";return v},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y", +TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925))*24*60*60*1E7,formatDate:function(a,b,c){if(!b)return"";var e=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,f=(c?c.dayNames:null)||this._defaults.dayNames,h=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort;c=(c?c.monthNames:null)||this._defaults.monthNames;var i=function(o){(o=k+112?a.getHours()+2:0);return a},_setDate:function(a,b,c){var e=!b,f=a.selectedMonth,h=a.selectedYear;b=this._restrictMinMax(a,this._determineDate(a,b,new Date));a.selectedDay= +a.currentDay=b.getDate();a.drawMonth=a.selectedMonth=a.currentMonth=b.getMonth();a.drawYear=a.selectedYear=a.currentYear=b.getFullYear();if((f!=a.selectedMonth||h!=a.selectedYear)&&!c)this._notifyChange(a);this._adjustInstDate(a);if(a.input)a.input.val(e?"":this._formatDate(a))},_getDate:function(a){return!a.currentYear||a.input&&a.input.val()==""?null:this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay))},_generateHTML:function(a){var b=new Date;b=this._daylightSavingAdjust(new Date(b.getFullYear(), +b.getMonth(),b.getDate()));var c=this._get(a,"isRTL"),e=this._get(a,"showButtonPanel"),f=this._get(a,"hideIfNoPrevNext"),h=this._get(a,"navigationAsDateFormat"),i=this._getNumberOfMonths(a),g=this._get(a,"showCurrentAtPos"),j=this._get(a,"stepMonths"),l=i[0]!=1||i[1]!=1,u=this._daylightSavingAdjust(!a.currentDay?new Date(9999,9,9):new Date(a.currentYear,a.currentMonth,a.currentDay)),k=this._getMinMaxDate(a,"min"),o=this._getMinMaxDate(a,"max");g=a.drawMonth-g;var m=a.drawYear;if(g<0){g+=12;m--}if(o){var n= +this._daylightSavingAdjust(new Date(o.getFullYear(),o.getMonth()-i[0]*i[1]+1,o.getDate()));for(n=k&&nn;){g--;if(g<0){g=11;m--}}}a.drawMonth=g;a.drawYear=m;n=this._get(a,"prevText");n=!h?n:this.formatDate(n,this._daylightSavingAdjust(new Date(m,g-j,1)),this._getFormatConfig(a));n=this._canAdjustMonth(a,-1,m,g)?''+n+"":f?"":''+n+"";var s=this._get(a,"nextText");s=!h?s:this.formatDate(s,this._daylightSavingAdjust(new Date(m,g+j,1)),this._getFormatConfig(a));f=this._canAdjustMonth(a,+1,m,g)?''+s+"":f?"":''+s+"";j=this._get(a,"currentText");s=this._get(a,"gotoCurrent")&&a.currentDay?u:b;j=!h?j:this.formatDate(j,s,this._getFormatConfig(a));h=!a.inline?'":"";e=e?'
    '+(c?h:"")+(this._isInRange(a,s)?'":"")+(c?"":h)+"
    ":"";h=parseInt(this._get(a,"firstDay"),10);h=isNaN(h)?0:h;j=this._get(a,"showWeek");s=this._get(a,"dayNames");this._get(a,"dayNamesShort");var q=this._get(a,"dayNamesMin"),B= +this._get(a,"monthNames"),v=this._get(a,"monthNamesShort"),p=this._get(a,"beforeShowDay"),D=this._get(a,"showOtherMonths"),K=this._get(a,"selectOtherMonths");this._get(a,"calculateWeek");for(var E=this._getDefaultDate(a),w="",x=0;x1)switch(G){case 0:y+=" ui-datepicker-group-first";t=" ui-corner-"+(c?"right": +"left");break;case i[1]-1:y+=" ui-datepicker-group-last";t=" ui-corner-"+(c?"left":"right");break;default:y+=" ui-datepicker-group-middle";t="";break}y+='">'}y+='
    '+(/all|left/.test(t)&&x==0?c?f:n:"")+(/all|right/.test(t)&&x==0?c?n:f:"")+this._generateMonthYearHeader(a,g,m,k,o,x>0||G>0,B,v)+'
    ';var z=j?'": +"";for(t=0;t<7;t++){var r=(t+h)%7;z+="=5?' class="ui-datepicker-week-end"':"")+'>'+q[r]+""}y+=z+"";z=this._getDaysInMonth(m,g);if(m==a.selectedYear&&g==a.selectedMonth)a.selectedDay=Math.min(a.selectedDay,z);t=(this._getFirstDayOfMonth(m,g)-h+7)%7;z=Math.ceil((t+z)/7);this.maxRows=z=l?this.maxRows>z?this.maxRows:z:z;r=this._daylightSavingAdjust(new Date(m,g,1-t));for(var Q=0;Q";var R=!j?"":'";for(t=0;t<7;t++){var I=p?p.apply(a.input?a.input[0]:null,[r]):[true,""],F=r.getMonth()!=g,L=F&&!K||!I[0]||k&&ro;R+='";r.setDate(r.getDate()+1);r=this._daylightSavingAdjust(r)}y+=R+""}g++;if(g>11){g=0;m++}y+="
    '+this._get(a,"weekHeader")+"
    '+ +this._get(a,"calculateWeek")(r)+""+(F&&!D?" ":L?''+r.getDate()+"":''+ +r.getDate()+"")+"
    "+(l?""+(i[0]>0&&G==i[1]-1?'
    ':""):"");O+=y}w+=O}w+=e+(d.browser.msie&&parseInt(d.browser.version,10)<7&&!a.inline?'':"");a._keyEvent=false;return w},_generateMonthYearHeader:function(a,b,c,e,f,h,i,g){var j=this._get(a,"changeMonth"), +l=this._get(a,"changeYear"),u=this._get(a,"showMonthAfterYear"),k='
    ',o="";if(h||!j)o+=''+i[b]+"";else{i=e&&e.getFullYear()==c;var m=f&&f.getFullYear()==c;o+='"}u||(k+=o+(h||!(j&&l)?" ":""));if(!a.yearshtml){a.yearshtml="";if(h||!l)k+=''+c+"";else{g=this._get(a,"yearRange").split(":");var s=(new Date).getFullYear();i=function(q){q=q.match(/c[+-].*/)?c+parseInt(q.substring(1),10):q.match(/[+-].*/)?s+parseInt(q,10):parseInt(q,10);return isNaN(q)?s:q};b=i(g[0]);g=Math.max(b,i(g[1]||""));b=e?Math.max(b,e.getFullYear()):b;g=f?Math.min(g,f.getFullYear()): +g;for(a.yearshtml+='";k+=a.yearshtml;a.yearshtml=null}}k+=this._get(a,"yearSuffix");if(u)k+=(h||!(j&&l)?" ":"")+o;k+="
    ";return k},_adjustInstDate:function(a,b,c){var e=a.drawYear+(c== +"Y"?b:0),f=a.drawMonth+(c=="M"?b:0);b=Math.min(a.selectedDay,this._getDaysInMonth(e,f))+(c=="D"?b:0);e=this._restrictMinMax(a,this._daylightSavingAdjust(new Date(e,f,b)));a.selectedDay=e.getDate();a.drawMonth=a.selectedMonth=e.getMonth();a.drawYear=a.selectedYear=e.getFullYear();if(c=="M"||c=="Y")this._notifyChange(a)},_restrictMinMax:function(a,b){var c=this._getMinMaxDate(a,"min");a=this._getMinMaxDate(a,"max");b=c&&ba?a:b},_notifyChange:function(a){var b=this._get(a,"onChangeMonthYear"); +if(b)b.apply(a.input?a.input[0]:null,[a.selectedYear,a.selectedMonth+1,a])},_getNumberOfMonths:function(a){a=this._get(a,"numberOfMonths");return a==null?[1,1]:typeof a=="number"?[1,a]:a},_getMinMaxDate:function(a,b){return this._determineDate(a,this._get(a,b+"Date"),null)},_getDaysInMonth:function(a,b){return 32-this._daylightSavingAdjust(new Date(a,b,32)).getDate()},_getFirstDayOfMonth:function(a,b){return(new Date(a,b,1)).getDay()},_canAdjustMonth:function(a,b,c,e){var f=this._getNumberOfMonths(a); +c=this._daylightSavingAdjust(new Date(c,e+(b<0?b:f[0]*f[1]),1));b<0&&c.setDate(this._getDaysInMonth(c.getFullYear(),c.getMonth()));return this._isInRange(a,c)},_isInRange:function(a,b){var c=this._getMinMaxDate(a,"min");a=this._getMinMaxDate(a,"max");return(!c||b.getTime()>=c.getTime())&&(!a||b.getTime()<=a.getTime())},_getFormatConfig:function(a){var b=this._get(a,"shortYearCutoff");b=typeof b!="string"?b:(new Date).getFullYear()%100+parseInt(b,10);return{shortYearCutoff:b,dayNamesShort:this._get(a, +"dayNamesShort"),dayNames:this._get(a,"dayNames"),monthNamesShort:this._get(a,"monthNamesShort"),monthNames:this._get(a,"monthNames")}},_formatDate:function(a,b,c,e){if(!b){a.currentDay=a.selectedDay;a.currentMonth=a.selectedMonth;a.currentYear=a.selectedYear}b=b?typeof b=="object"?b:this._daylightSavingAdjust(new Date(e,c,b)):this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return this.formatDate(this._get(a,"dateFormat"),b,this._getFormatConfig(a))}});d.fn.datepicker= +function(a){if(!this.length)return this;if(!d.datepicker.initialized){d(document).mousedown(d.datepicker._checkExternalClick).find("body").append(d.datepicker.dpDiv);d.datepicker.initialized=true}var b=Array.prototype.slice.call(arguments,1);if(typeof a=="string"&&(a=="isDisabled"||a=="getDate"||a=="widget"))return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this[0]].concat(b));if(a=="option"&&arguments.length==2&&typeof arguments[1]=="string")return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker, +[this[0]].concat(b));return this.each(function(){typeof a=="string"?d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this].concat(b)):d.datepicker._attachDatepicker(this,a)})};d.datepicker=new M;d.datepicker.initialized=false;d.datepicker.uuid=(new Date).getTime();d.datepicker.version="1.8.14";window["DP_jQuery_"+A]=d})(jQuery); +;/* + * jQuery UI Progressbar 1.8.14 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Progressbar + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + */ +(function(b,d){b.widget("ui.progressbar",{options:{value:0,max:100},min:0,_create:function(){this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this.min,"aria-valuemax":this.options.max,"aria-valuenow":this._value()});this.valueDiv=b("
    ").appendTo(this.element);this.oldValue=this._value();this._refreshValue()},destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"); +this.valueDiv.remove();b.Widget.prototype.destroy.apply(this,arguments)},value:function(a){if(a===d)return this._value();this._setOption("value",a);return this},_setOption:function(a,c){if(a==="value"){this.options.value=c;this._refreshValue();this._value()===this.options.max&&this._trigger("complete")}b.Widget.prototype._setOption.apply(this,arguments)},_value:function(){var a=this.options.value;if(typeof a!=="number")a=0;return Math.min(this.options.max,Math.max(this.min,a))},_percentage:function(){return 100* +this._value()/this.options.max},_refreshValue:function(){var a=this.value(),c=this._percentage();if(this.oldValue!==a){this.oldValue=a;this._trigger("change")}this.valueDiv.toggle(a>this.min).toggleClass("ui-corner-right",a===this.options.max).width(c.toFixed(0)+"%");this.element.attr("aria-valuenow",a)}});b.extend(b.ui.progressbar,{version:"1.8.14"})})(jQuery); +;/* + * jQuery UI Effects 1.8.14 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/ + */ +jQuery.effects||function(f,j){function m(c){var a;if(c&&c.constructor==Array&&c.length==3)return c;if(a=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(c))return[parseInt(a[1],10),parseInt(a[2],10),parseInt(a[3],10)];if(a=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(c))return[parseFloat(a[1])*2.55,parseFloat(a[2])*2.55,parseFloat(a[3])*2.55];if(a=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(c))return[parseInt(a[1], +16),parseInt(a[2],16),parseInt(a[3],16)];if(a=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(c))return[parseInt(a[1]+a[1],16),parseInt(a[2]+a[2],16),parseInt(a[3]+a[3],16)];if(/rgba\(0, 0, 0, 0\)/.exec(c))return n.transparent;return n[f.trim(c).toLowerCase()]}function s(c,a){var b;do{b=f.curCSS(c,a);if(b!=""&&b!="transparent"||f.nodeName(c,"body"))break;a="backgroundColor"}while(c=c.parentNode);return m(b)}function o(){var c=document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle, +a={},b,d;if(c&&c.length&&c[0]&&c[c[0]])for(var e=c.length;e--;){b=c[e];if(typeof c[b]=="string"){d=b.replace(/\-(\w)/g,function(g,h){return h.toUpperCase()});a[d]=c[b]}}else for(b in c)if(typeof c[b]==="string")a[b]=c[b];return a}function p(c){var a,b;for(a in c){b=c[a];if(b==null||f.isFunction(b)||a in t||/scrollbar/.test(a)||!/color/i.test(a)&&isNaN(parseFloat(b)))delete c[a]}return c}function u(c,a){var b={_:0},d;for(d in a)if(c[d]!=a[d])b[d]=a[d];return b}function k(c,a,b,d){if(typeof c=="object"){d= +a;b=null;a=c;c=a.effect}if(f.isFunction(a)){d=a;b=null;a={}}if(typeof a=="number"||f.fx.speeds[a]){d=b;b=a;a={}}if(f.isFunction(b)){d=b;b=null}a=a||{};b=b||a.duration;b=f.fx.off?0:typeof b=="number"?b:b in f.fx.speeds?f.fx.speeds[b]:f.fx.speeds._default;d=d||a.complete;return[c,a,b,d]}function l(c){if(!c||typeof c==="number"||f.fx.speeds[c])return true;if(typeof c==="string"&&!f.effects[c])return true;return false}f.effects={};f.each(["backgroundColor","borderBottomColor","borderLeftColor","borderRightColor", +"borderTopColor","borderColor","color","outlineColor"],function(c,a){f.fx.step[a]=function(b){if(!b.colorInit){b.start=s(b.elem,a);b.end=m(b.end);b.colorInit=true}b.elem.style[a]="rgb("+Math.max(Math.min(parseInt(b.pos*(b.end[0]-b.start[0])+b.start[0],10),255),0)+","+Math.max(Math.min(parseInt(b.pos*(b.end[1]-b.start[1])+b.start[1],10),255),0)+","+Math.max(Math.min(parseInt(b.pos*(b.end[2]-b.start[2])+b.start[2],10),255),0)+")"}});var n={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0, +0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211, +211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0],transparent:[255,255,255]},q=["add","remove","toggle"],t={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};f.effects.animateClass=function(c,a,b, +d){if(f.isFunction(b)){d=b;b=null}return this.queue(function(){var e=f(this),g=e.attr("style")||" ",h=p(o.call(this)),r,v=e.attr("class");f.each(q,function(w,i){c[i]&&e[i+"Class"](c[i])});r=p(o.call(this));e.attr("class",v);e.animate(u(h,r),{queue:false,duration:a,easing:b,complete:function(){f.each(q,function(w,i){c[i]&&e[i+"Class"](c[i])});if(typeof e.attr("style")=="object"){e.attr("style").cssText="";e.attr("style").cssText=g}else e.attr("style",g);d&&d.apply(this,arguments);f.dequeue(this)}})})}; +f.fn.extend({_addClass:f.fn.addClass,addClass:function(c,a,b,d){return a?f.effects.animateClass.apply(this,[{add:c},a,b,d]):this._addClass(c)},_removeClass:f.fn.removeClass,removeClass:function(c,a,b,d){return a?f.effects.animateClass.apply(this,[{remove:c},a,b,d]):this._removeClass(c)},_toggleClass:f.fn.toggleClass,toggleClass:function(c,a,b,d,e){return typeof a=="boolean"||a===j?b?f.effects.animateClass.apply(this,[a?{add:c}:{remove:c},b,d,e]):this._toggleClass(c,a):f.effects.animateClass.apply(this, +[{toggle:c},a,b,d])},switchClass:function(c,a,b,d,e){return f.effects.animateClass.apply(this,[{add:a,remove:c},b,d,e])}});f.extend(f.effects,{version:"1.8.14",save:function(c,a){for(var b=0;b").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}); +c.wrap(b);b=c.parent();if(c.css("position")=="static"){b.css({position:"relative"});c.css({position:"relative"})}else{f.extend(a,{position:c.css("position"),zIndex:c.css("z-index")});f.each(["top","left","bottom","right"],function(d,e){a[e]=c.css(e);if(isNaN(parseInt(a[e],10)))a[e]="auto"});c.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})}return b.css(a).show()},removeWrapper:function(c){if(c.parent().is(".ui-effects-wrapper"))return c.parent().replaceWith(c);return c},setTransition:function(c, +a,b,d){d=d||{};f.each(a,function(e,g){unit=c.cssUnit(g);if(unit[0]>0)d[g]=unit[0]*b+unit[1]});return d}});f.fn.extend({effect:function(c){var a=k.apply(this,arguments),b={options:a[1],duration:a[2],callback:a[3]};a=b.options.mode;var d=f.effects[c];if(f.fx.off||!d)return a?this[a](b.duration,b.callback):this.each(function(){b.callback&&b.callback.call(this)});return d.call(this,b)},_show:f.fn.show,show:function(c){if(l(c))return this._show.apply(this,arguments);else{var a=k.apply(this,arguments); +a[1].mode="show";return this.effect.apply(this,a)}},_hide:f.fn.hide,hide:function(c){if(l(c))return this._hide.apply(this,arguments);else{var a=k.apply(this,arguments);a[1].mode="hide";return this.effect.apply(this,a)}},__toggle:f.fn.toggle,toggle:function(c){if(l(c)||typeof c==="boolean"||f.isFunction(c))return this.__toggle.apply(this,arguments);else{var a=k.apply(this,arguments);a[1].mode="toggle";return this.effect.apply(this,a)}},cssUnit:function(c){var a=this.css(c),b=[];f.each(["em","px","%", +"pt"],function(d,e){if(a.indexOf(e)>0)b=[parseFloat(a),e]});return b}});f.easing.jswing=f.easing.swing;f.extend(f.easing,{def:"easeOutQuad",swing:function(c,a,b,d,e){return f.easing[f.easing.def](c,a,b,d,e)},easeInQuad:function(c,a,b,d,e){return d*(a/=e)*a+b},easeOutQuad:function(c,a,b,d,e){return-d*(a/=e)*(a-2)+b},easeInOutQuad:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a+b;return-d/2*(--a*(a-2)-1)+b},easeInCubic:function(c,a,b,d,e){return d*(a/=e)*a*a+b},easeOutCubic:function(c,a,b,d,e){return d* +((a=a/e-1)*a*a+1)+b},easeInOutCubic:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a+b;return d/2*((a-=2)*a*a+2)+b},easeInQuart:function(c,a,b,d,e){return d*(a/=e)*a*a*a+b},easeOutQuart:function(c,a,b,d,e){return-d*((a=a/e-1)*a*a*a-1)+b},easeInOutQuart:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a*a+b;return-d/2*((a-=2)*a*a*a-2)+b},easeInQuint:function(c,a,b,d,e){return d*(a/=e)*a*a*a*a+b},easeOutQuint:function(c,a,b,d,e){return d*((a=a/e-1)*a*a*a*a+1)+b},easeInOutQuint:function(c,a,b,d,e){if((a/= +e/2)<1)return d/2*a*a*a*a*a+b;return d/2*((a-=2)*a*a*a*a+2)+b},easeInSine:function(c,a,b,d,e){return-d*Math.cos(a/e*(Math.PI/2))+d+b},easeOutSine:function(c,a,b,d,e){return d*Math.sin(a/e*(Math.PI/2))+b},easeInOutSine:function(c,a,b,d,e){return-d/2*(Math.cos(Math.PI*a/e)-1)+b},easeInExpo:function(c,a,b,d,e){return a==0?b:d*Math.pow(2,10*(a/e-1))+b},easeOutExpo:function(c,a,b,d,e){return a==e?b+d:d*(-Math.pow(2,-10*a/e)+1)+b},easeInOutExpo:function(c,a,b,d,e){if(a==0)return b;if(a==e)return b+d;if((a/= +e/2)<1)return d/2*Math.pow(2,10*(a-1))+b;return d/2*(-Math.pow(2,-10*--a)+2)+b},easeInCirc:function(c,a,b,d,e){return-d*(Math.sqrt(1-(a/=e)*a)-1)+b},easeOutCirc:function(c,a,b,d,e){return d*Math.sqrt(1-(a=a/e-1)*a)+b},easeInOutCirc:function(c,a,b,d,e){if((a/=e/2)<1)return-d/2*(Math.sqrt(1-a*a)-1)+b;return d/2*(Math.sqrt(1-(a-=2)*a)+1)+b},easeInElastic:function(c,a,b,d,e){c=1.70158;var g=0,h=d;if(a==0)return b;if((a/=e)==1)return b+d;g||(g=e*0.3);if(h").css({position:"absolute",visibility:"visible",left:-f*(h/d),top:-e*(i/c)}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:h/d,height:i/c,left:g.left+f*(h/d)+(a.options.mode=="show"?(f-Math.floor(d/2))*(h/d):0),top:g.top+e*(i/c)+(a.options.mode=="show"?(e-Math.floor(c/2))*(i/c):0),opacity:a.options.mode=="show"?0:1}).animate({left:g.left+f*(h/d)+(a.options.mode=="show"?0:(f-Math.floor(d/2))*(h/d)),top:g.top+ +e*(i/c)+(a.options.mode=="show"?0:(e-Math.floor(c/2))*(i/c)),opacity:a.options.mode=="show"?1:0},a.duration||500);setTimeout(function(){a.options.mode=="show"?b.css({visibility:"visible"}):b.css({visibility:"visible"}).hide();a.callback&&a.callback.apply(b[0]);b.dequeue();j("div.ui-effects-explode").remove()},a.duration||500)})}})(jQuery); +;/* + * jQuery UI Effects Fade 1.8.14 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Fade + * + * Depends: + * jquery.effects.core.js + */ +(function(b){b.effects.fade=function(a){return this.queue(function(){var c=b(this),d=b.effects.setMode(c,a.options.mode||"hide");c.animate({opacity:d},{queue:false,duration:a.duration,easing:a.options.easing,complete:function(){a.callback&&a.callback.apply(this,arguments);c.dequeue()}})})}})(jQuery); +;/* + * jQuery UI Effects Fold 1.8.14 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Fold + * + * Depends: + * jquery.effects.core.js + */ +(function(c){c.effects.fold=function(a){return this.queue(function(){var b=c(this),j=["position","top","bottom","left","right"],d=c.effects.setMode(b,a.options.mode||"hide"),g=a.options.size||15,h=!!a.options.horizFirst,k=a.duration?a.duration/2:c.fx.speeds._default/2;c.effects.save(b,j);b.show();var e=c.effects.createWrapper(b).css({overflow:"hidden"}),f=d=="show"!=h,l=f?["width","height"]:["height","width"];f=f?[e.width(),e.height()]:[e.height(),e.width()];var i=/([0-9]+)%/.exec(g);if(i)g=parseInt(i[1], +10)/100*f[d=="hide"?0:1];if(d=="show")e.css(h?{height:0,width:g}:{height:g,width:0});h={};i={};h[l[0]]=d=="show"?f[0]:g;i[l[1]]=d=="show"?f[1]:0;e.animate(h,k,a.options.easing).animate(i,k,a.options.easing,function(){d=="hide"&&b.hide();c.effects.restore(b,j);c.effects.removeWrapper(b);a.callback&&a.callback.apply(b[0],arguments);b.dequeue()})})}})(jQuery); +;/* + * jQuery UI Effects Highlight 1.8.14 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Highlight + * + * Depends: + * jquery.effects.core.js + */ +(function(b){b.effects.highlight=function(c){return this.queue(function(){var a=b(this),e=["backgroundImage","backgroundColor","opacity"],d=b.effects.setMode(a,c.options.mode||"show"),f={backgroundColor:a.css("backgroundColor")};if(d=="hide")f.opacity=0;b.effects.save(a,e);a.show().css({backgroundImage:"none",backgroundColor:c.options.color||"#ffff99"}).animate(f,{queue:false,duration:c.duration,easing:c.options.easing,complete:function(){d=="hide"&&a.hide();b.effects.restore(a,e);d=="show"&&!b.support.opacity&& +this.style.removeAttribute("filter");c.callback&&c.callback.apply(this,arguments);a.dequeue()}})})}})(jQuery); +;/* + * jQuery UI Effects Pulsate 1.8.14 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Pulsate + * + * Depends: + * jquery.effects.core.js + */ +(function(d){d.effects.pulsate=function(a){return this.queue(function(){var b=d(this),c=d.effects.setMode(b,a.options.mode||"show");times=(a.options.times||5)*2-1;duration=a.duration?a.duration/2:d.fx.speeds._default/2;isVisible=b.is(":visible");animateTo=0;if(!isVisible){b.css("opacity",0).show();animateTo=1}if(c=="hide"&&isVisible||c=="show"&&!isVisible)times--;for(c=0;c').appendTo(document.body).addClass(a.options.className).css({top:d.top,left:d.left,height:b.innerHeight(),width:b.innerWidth(),position:"absolute"}).animate(c,a.duration,a.options.easing,function(){f.remove();a.callback&&a.callback.apply(b[0],arguments); +b.dequeue()})})}})(jQuery); +; \ No newline at end of file diff --git a/cosmic-client/cosmic-ui/lib/jquery.cookies.js b/cosmic-client/cosmic-ui/lib/jquery.cookies.js new file mode 100644 index 0000000000..6df1faca25 --- /dev/null +++ b/cosmic-client/cosmic-ui/lib/jquery.cookies.js @@ -0,0 +1,96 @@ +/** + * Cookie plugin + * + * Copyright (c) 2006 Klaus Hartl (stilbuero.de) + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + */ + +/** + * Create a cookie with the given name and value and other optional parameters. + * + * @example $.cookie('the_cookie', 'the_value'); + * @desc Set the value of a cookie. + * @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true }); + * @desc Create a cookie with all available options. + * @example $.cookie('the_cookie', 'the_value'); + * @desc Create a session cookie. + * @example $.cookie('the_cookie', null); + * @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain + * used when the cookie was set. + * + * @param String name The name of the cookie. + * @param String value The value of the cookie. + * @param Object options An object literal containing key/value pairs to provide optional cookie attributes. + * @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object. + * If a negative value is specified (e.g. a date in the past), the cookie will be deleted. + * If set to null or omitted, the cookie will be a session cookie and will not be retained + * when the the browser exits. + * @option String path The value of the path atribute of the cookie (default: path of page that created the cookie). + * @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie). + * @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will + * require a secure protocol (like HTTPS). + * @type undefined + * + * @name $.cookie + * @cat Plugins/Cookie + * @author Klaus Hartl/klaus.hartl@stilbuero.de + */ + +/** + * Get the value of a cookie with the given name. + * + * @example $.cookie('the_cookie'); + * @desc Get the value of a cookie. + * + * @param String name The name of the cookie. + * @return The value of the cookie. + * @type String + * + * @name $.cookie + * @cat Plugins/Cookie + * @author Klaus Hartl/klaus.hartl@stilbuero.de + */ +jQuery.cookie = function(name, value, options) { + if (typeof value != 'undefined') { // name and value given, set cookie + options = options || {}; + if (value === null) { + value = ''; + options.expires = -1; + } + var expires = ''; + if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) { + var date; + if (typeof options.expires == 'number') { + date = new Date(); + date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000)); + } else { + date = options.expires; + } + expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE + } + // CAUTION: Needed to parenthesize options.path and options.domain + // in the following expressions, otherwise they evaluate to undefined + // in the packed version for some reason... + var path = options.path ? '; path=' + (options.path) : ''; + var domain = options.domain ? '; domain=' + (options.domain) : ''; + var secure = options.secure ? '; secure' : ''; + document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join(''); + } else { // only name given, get cookie + var cookieValue = null; + if (document.cookie && document.cookie != '') { + var cookies = document.cookie.split(';'); + for (var i = 0; i < cookies.length; i++) { + var cookie = jQuery.trim(cookies[i]); + // Does this cookie string begin with the name we want? + if (cookie.substring(0, name.length + 1) == (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; + } +}; \ No newline at end of file diff --git a/cosmic-client/cosmic-ui/lib/jquery.easing.js b/cosmic-client/cosmic-ui/lib/jquery.easing.js new file mode 100644 index 0000000000..31587dd086 --- /dev/null +++ b/cosmic-client/cosmic-ui/lib/jquery.easing.js @@ -0,0 +1,205 @@ +/* + * jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/ + * + * Uses the built in easing capabilities added In jQuery 1.1 + * to offer multiple easing options + * + * TERMS OF USE - jQuery Easing + * + * Open source under the BSD License. + * + * Copyright © 2008 George McGinley Smith + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the author nor the names of contributors may be used to endorse + * or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * +*/ + +// t: current time, b: begInnIng value, c: change In value, d: duration +jQuery.easing['jswing'] = jQuery.easing['swing']; + +jQuery.extend( jQuery.easing, +{ + def: 'easeOutQuad', + swing: function (x, t, b, c, d) { + //alert(jQuery.easing.default); + return jQuery.easing[jQuery.easing.def](x, t, b, c, d); + }, + easeInQuad: function (x, t, b, c, d) { + return c*(t/=d)*t + b; + }, + easeOutQuad: function (x, t, b, c, d) { + return -c *(t/=d)*(t-2) + b; + }, + easeInOutQuad: function (x, t, b, c, d) { + if ((t/=d/2) < 1) return c/2*t*t + b; + return -c/2 * ((--t)*(t-2) - 1) + b; + }, + easeInCubic: function (x, t, b, c, d) { + return c*(t/=d)*t*t + b; + }, + easeOutCubic: function (x, t, b, c, d) { + return c*((t=t/d-1)*t*t + 1) + b; + }, + easeInOutCubic: function (x, t, b, c, d) { + if ((t/=d/2) < 1) return c/2*t*t*t + b; + return c/2*((t-=2)*t*t + 2) + b; + }, + easeInQuart: function (x, t, b, c, d) { + return c*(t/=d)*t*t*t + b; + }, + easeOutQuart: function (x, t, b, c, d) { + return -c * ((t=t/d-1)*t*t*t - 1) + b; + }, + easeInOutQuart: function (x, t, b, c, d) { + if ((t/=d/2) < 1) return c/2*t*t*t*t + b; + return -c/2 * ((t-=2)*t*t*t - 2) + b; + }, + easeInQuint: function (x, t, b, c, d) { + return c*(t/=d)*t*t*t*t + b; + }, + easeOutQuint: function (x, t, b, c, d) { + return c*((t=t/d-1)*t*t*t*t + 1) + b; + }, + easeInOutQuint: function (x, t, b, c, d) { + if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b; + return c/2*((t-=2)*t*t*t*t + 2) + b; + }, + easeInSine: function (x, t, b, c, d) { + return -c * Math.cos(t/d * (Math.PI/2)) + c + b; + }, + easeOutSine: function (x, t, b, c, d) { + return c * Math.sin(t/d * (Math.PI/2)) + b; + }, + easeInOutSine: function (x, t, b, c, d) { + return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b; + }, + easeInExpo: function (x, t, b, c, d) { + return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b; + }, + easeOutExpo: function (x, t, b, c, d) { + return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b; + }, + easeInOutExpo: function (x, t, b, c, d) { + if (t==0) return b; + if (t==d) return b+c; + if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b; + return c/2 * (-Math.pow(2, -10 * --t) + 2) + b; + }, + easeInCirc: function (x, t, b, c, d) { + return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b; + }, + easeOutCirc: function (x, t, b, c, d) { + return c * Math.sqrt(1 - (t=t/d-1)*t) + b; + }, + easeInOutCirc: function (x, t, b, c, d) { + if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b; + return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b; + }, + easeInElastic: function (x, t, b, c, d) { + var s=1.70158;var p=0;var a=c; + if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3; + if (a < Math.abs(c)) { a=c; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (c/a); + return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b; + }, + easeOutElastic: function (x, t, b, c, d) { + var s=1.70158;var p=0;var a=c; + if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3; + if (a < Math.abs(c)) { a=c; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (c/a); + return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b; + }, + easeInOutElastic: function (x, t, b, c, d) { + var s=1.70158;var p=0;var a=c; + if (t==0) return b; if ((t/=d/2)==2) return b+c; if (!p) p=d*(.3*1.5); + if (a < Math.abs(c)) { a=c; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (c/a); + if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b; + return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b; + }, + easeInBack: function (x, t, b, c, d, s) { + if (s == undefined) s = 1.70158; + return c*(t/=d)*t*((s+1)*t - s) + b; + }, + easeOutBack: function (x, t, b, c, d, s) { + if (s == undefined) s = 1.70158; + return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b; + }, + easeInOutBack: function (x, t, b, c, d, s) { + if (s == undefined) s = 1.70158; + if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b; + return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b; + }, + easeInBounce: function (x, t, b, c, d) { + return c - jQuery.easing.easeOutBounce (x, d-t, 0, c, d) + b; + }, + easeOutBounce: function (x, t, b, c, d) { + if ((t/=d) < (1/2.75)) { + return c*(7.5625*t*t) + b; + } else if (t < (2/2.75)) { + return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b; + } else if (t < (2.5/2.75)) { + return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b; + } else { + return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b; + } + }, + easeInOutBounce: function (x, t, b, c, d) { + if (t < d/2) return jQuery.easing.easeInBounce (x, t*2, 0, c, d) * .5 + b; + return jQuery.easing.easeOutBounce (x, t*2-d, 0, c, d) * .5 + c*.5 + b; + } +}); + +/* + * + * TERMS OF USE - EASING EQUATIONS + * + * Open source under the BSD License. + * + * Copyright © 2001 Robert Penner + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the author nor the names of contributors may be used to endorse + * or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ diff --git a/cosmic-client/cosmic-ui/lib/jquery.js b/cosmic-client/cosmic-ui/lib/jquery.js new file mode 100644 index 0000000000..11e6d06796 --- /dev/null +++ b/cosmic-client/cosmic-ui/lib/jquery.js @@ -0,0 +1,9046 @@ +/*! + * jQuery JavaScript Library v1.6.4 + * http://jquery.com/ + * + * Copyright 2011, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Mon Sep 12 18:54:48 2011 -0400 + */ +(function( window, undefined ) { + +// Use the correct document accordingly with window argument (sandbox) +var document = window.document, + navigator = window.navigator, + location = window.location; +var jQuery = (function() { + +// Define a local copy of jQuery +var jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context, rootjQuery ); + }, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // A central reference to the root jQuery(document) + rootjQuery, + + // A simple way to check for HTML strings or ID strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, + + // Check if a string has a non-whitespace character in it + rnotwhite = /\S/, + + // Used for trimming whitespace + trimLeft = /^\s+/, + trimRight = /\s+$/, + + // Check for digits + rdigit = /\d/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, + rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + + // Useragent RegExp + rwebkit = /(webkit)[ \/]([\w.]+)/, + ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, + rmsie = /(msie) ([\w.]+)/, + rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, + + // Matches dashed string for camelizing + rdashAlpha = /-([a-z]|[0-9])/ig, + rmsPrefix = /^-ms-/, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return ( letter + "" ).toUpperCase(); + }, + + // Keep a UserAgent string for use with jQuery.browser + userAgent = navigator.userAgent, + + // For matching the engine and version of the browser + browserMatch, + + // The deferred used on DOM ready + readyList, + + // The ready event handler + DOMContentLoaded, + + // Save a reference to some core methods + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + push = Array.prototype.push, + slice = Array.prototype.slice, + trim = String.prototype.trim, + indexOf = Array.prototype.indexOf, + + // [[Class]] -> type pairs + class2type = {}; + +jQuery.fn = jQuery.prototype = { + constructor: jQuery, + init: function( selector, context, rootjQuery ) { + var match, elem, ret, doc; + + // Handle $(""), $(null), or $(undefined) + if ( !selector ) { + return this; + } + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // The body element only exists once, optimize finding it + if ( selector === "body" && !context && document.body ) { + this.context = document; + this[0] = document.body; + this.selector = selector; + this.length = 1; + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = quickExpr.exec( selector ); + } + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + doc = (context ? context.ownerDocument || context : document); + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + ret = rsingleTag.exec( selector ); + + if ( ret ) { + if ( jQuery.isPlainObject( context ) ) { + selector = [ document.createElement( ret[1] ) ]; + jQuery.fn.attr.call( selector, context, true ); + + } else { + selector = [ doc.createElement( ret[1] ) ]; + } + + } else { + ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); + selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes; + } + + return jQuery.merge( this, selector ); + + // HANDLE: $("#id") + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return (context || rootjQuery).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if (selector.selector !== undefined) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.6.4", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return slice.call( this, 0 ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this[ this.length + num ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = this.constructor(); + + if ( jQuery.isArray( elems ) ) { + push.apply( ret, elems ); + + } else { + jQuery.merge( ret, elems ); + } + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) { + ret.selector = this.selector + (this.selector ? " " : "") + selector; + } else if ( name ) { + ret.selector = this.selector + "." + name + "(" + selector + ")"; + } + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Attach the listeners + jQuery.bindReady(); + + // Add the callback + readyList.done( fn ); + + return this; + }, + + eq: function( i ) { + return i === -1 ? + this.slice( i ) : + this.slice( i, +i + 1 ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ), + "slice", slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + if ( window.$ === jQuery ) { + window.$ = _$; + } + + if ( deep && window.jQuery === jQuery ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + // Either a released hold or an DOMready/load event and not yet ready + if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready, 1 ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger( "ready" ).unbind( "ready" ); + } + } + }, + + bindReady: function() { + if ( readyList ) { + return; + } + + readyList = jQuery._Deferred(); + + // Catch cases where $(document).ready() is called after the + // browser event has already occurred. + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + return setTimeout( jQuery.ready, 1 ); + } + + // Mozilla, Opera and webkit nightlies currently support this event + if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); + + // If IE event model is used + } else if ( document.attachEvent ) { + // ensure firing before onload, + // maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", DOMContentLoaded ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); + + // If IE and not a frame + // continually check to see if the document is ready + var toplevel = false; + + try { + toplevel = window.frameElement == null; + } catch(e) {} + + if ( document.documentElement.doScroll && toplevel ) { + doScrollCheck(); + } + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + // A crude way of determining if an object is a window + isWindow: function( obj ) { + return obj && typeof obj === "object" && "setInterval" in obj; + }, + + isNaN: function( obj ) { + return obj == null || !rdigit.test( obj ) || isNaN( obj ); + }, + + type: function( obj ) { + return obj == null ? + String( obj ) : + class2type[ toString.call(obj) ] || "object"; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + try { + // Not own constructor property must be Object + if ( obj.constructor && + !hasOwn.call(obj, "constructor") && + !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + } catch ( e ) { + // IE8,9 Will throw exceptions on certain host objects #9897 + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + for ( var name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw msg; + }, + + parseJSON: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + // Attempt to parse using the native JSON parser first + if ( window.JSON && window.JSON.parse ) { + return window.JSON.parse( data ); + } + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test( data.replace( rvalidescape, "@" ) + .replace( rvalidtokens, "]" ) + .replace( rvalidbraces, "")) ) { + + return (new Function( "return " + data ))(); + + } + jQuery.error( "Invalid JSON: " + data ); + }, + + // Cross-browser xml parsing + parseXML: function( data ) { + var xml, tmp; + try { + if ( window.DOMParser ) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString( data , "text/xml" ); + } else { // IE + xml = new ActiveXObject( "Microsoft.XMLDOM" ); + xml.async = "false"; + xml.loadXML( data ); + } + } catch( e ) { + xml = undefined; + } + if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; + }, + + noop: function() {}, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && rnotwhite.test( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, + length = object.length, + isObj = length === undefined || jQuery.isFunction( object ); + + if ( args ) { + if ( isObj ) { + for ( name in object ) { + if ( callback.apply( object[ name ], args ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.apply( object[ i++ ], args ) === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isObj ) { + for ( name in object ) { + if ( callback.call( object[ name ], name, object[ name ] ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) { + break; + } + } + } + } + + return object; + }, + + // Use native String.trim function wherever possible + trim: trim ? + function( text ) { + return text == null ? + "" : + trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); + }, + + // results is for internal usage only + makeArray: function( array, results ) { + var ret = results || []; + + if ( array != null ) { + // The window, strings (and functions) also have 'length' + // The extra typeof function check is to prevent crashes + // in Safari 2 (See: #3039) + // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 + var type = jQuery.type( array ); + + if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { + push.call( ret, array ); + } else { + jQuery.merge( ret, array ); + } + } + + return ret; + }, + + inArray: function( elem, array ) { + if ( !array ) { + return -1; + } + + if ( indexOf ) { + return indexOf.call( array, elem ); + } + + for ( var i = 0, length = array.length; i < length; i++ ) { + if ( array[ i ] === elem ) { + return i; + } + } + + return -1; + }, + + merge: function( first, second ) { + var i = first.length, + j = 0; + + if ( typeof second.length === "number" ) { + for ( var l = second.length; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var ret = [], retVal; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( var i = 0, length = elems.length; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, key, ret = [], + i = 0, + length = elems.length, + // jquery objects are treated as arrays + isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ; + + // Go through the array, translating each of the items to their + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + // Go through every key on the object, + } else { + for ( key in elems ) { + value = callback( elems[ key ], key, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + } + + // Flatten any nested arrays + return ret.concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + if ( typeof context === "string" ) { + var tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + var args = slice.call( arguments, 2 ), + proxy = function() { + return fn.apply( context, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + + return proxy; + }, + + // Mutifunctional method to get and set values to a collection + // The value/s can optionally be executed if it's a function + access: function( elems, key, value, exec, fn, pass ) { + var length = elems.length; + + // Setting many attributes + if ( typeof key === "object" ) { + for ( var k in key ) { + jQuery.access( elems, k, key[k], exec, fn, value ); + } + return elems; + } + + // Setting one attribute + if ( value !== undefined ) { + // Optionally, function values get executed if exec is true + exec = !pass && exec && jQuery.isFunction(value); + + for ( var i = 0; i < length; i++ ) { + fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + } + + return elems; + } + + // Getting an attribute + return length ? fn( elems[0], key ) : undefined; + }, + + now: function() { + return (new Date()).getTime(); + }, + + // Use of jQuery.browser is frowned upon. + // More details: http://docs.jquery.com/Utilities/jQuery.browser + uaMatch: function( ua ) { + ua = ua.toLowerCase(); + + var match = rwebkit.exec( ua ) || + ropera.exec( ua ) || + rmsie.exec( ua ) || + ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || + []; + + return { browser: match[1] || "", version: match[2] || "0" }; + }, + + sub: function() { + function jQuerySub( selector, context ) { + return new jQuerySub.fn.init( selector, context ); + } + jQuery.extend( true, jQuerySub, this ); + jQuerySub.superclass = this; + jQuerySub.fn = jQuerySub.prototype = this(); + jQuerySub.fn.constructor = jQuerySub; + jQuerySub.sub = this.sub; + jQuerySub.fn.init = function init( selector, context ) { + if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) { + context = jQuerySub( context ); + } + + return jQuery.fn.init.call( this, selector, context, rootjQuerySub ); + }; + jQuerySub.fn.init.prototype = jQuerySub.fn; + var rootjQuerySub = jQuerySub(document); + return jQuerySub; + }, + + browser: {} +}); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +browserMatch = jQuery.uaMatch( userAgent ); +if ( browserMatch.browser ) { + jQuery.browser[ browserMatch.browser ] = true; + jQuery.browser.version = browserMatch.version; +} + +// Deprecated, use jQuery.browser.webkit instead +if ( jQuery.browser.webkit ) { + jQuery.browser.safari = true; +} + +// IE doesn't match non-breaking spaces with \s +if ( rnotwhite.test( "\xA0" ) ) { + trimLeft = /^[\s\xA0]+/; + trimRight = /[\s\xA0]+$/; +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); + +// Cleanup functions for the document ready method +if ( document.addEventListener ) { + DOMContentLoaded = function() { + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + jQuery.ready(); + }; + +} else if ( document.attachEvent ) { + DOMContentLoaded = function() { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( document.readyState === "complete" ) { + document.detachEvent( "onreadystatechange", DOMContentLoaded ); + jQuery.ready(); + } + }; +} + +// The DOM ready check for Internet Explorer +function doScrollCheck() { + if ( jQuery.isReady ) { + return; + } + + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch(e) { + setTimeout( doScrollCheck, 1 ); + return; + } + + // and execute any waiting functions + jQuery.ready(); +} + +return jQuery; + +})(); + + +var // Promise methods + promiseMethods = "done fail isResolved isRejected promise then always pipe".split( " " ), + // Static reference to slice + sliceDeferred = [].slice; + +jQuery.extend({ + // Create a simple deferred (one callbacks list) + _Deferred: function() { + var // callbacks list + callbacks = [], + // stored [ context , args ] + fired, + // to avoid firing when already doing so + firing, + // flag to know if the deferred has been cancelled + cancelled, + // the deferred itself + deferred = { + + // done( f1, f2, ...) + done: function() { + if ( !cancelled ) { + var args = arguments, + i, + length, + elem, + type, + _fired; + if ( fired ) { + _fired = fired; + fired = 0; + } + for ( i = 0, length = args.length; i < length; i++ ) { + elem = args[ i ]; + type = jQuery.type( elem ); + if ( type === "array" ) { + deferred.done.apply( deferred, elem ); + } else if ( type === "function" ) { + callbacks.push( elem ); + } + } + if ( _fired ) { + deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] ); + } + } + return this; + }, + + // resolve with given context and args + resolveWith: function( context, args ) { + if ( !cancelled && !fired && !firing ) { + // make sure args are available (#8421) + args = args || []; + firing = 1; + try { + while( callbacks[ 0 ] ) { + callbacks.shift().apply( context, args ); + } + } + finally { + fired = [ context, args ]; + firing = 0; + } + } + return this; + }, + + // resolve with this as context and given arguments + resolve: function() { + deferred.resolveWith( this, arguments ); + return this; + }, + + // Has this deferred been resolved? + isResolved: function() { + return !!( firing || fired ); + }, + + // Cancel + cancel: function() { + cancelled = 1; + callbacks = []; + return this; + } + }; + + return deferred; + }, + + // Full fledged deferred (two callbacks list) + Deferred: function( func ) { + var deferred = jQuery._Deferred(), + failDeferred = jQuery._Deferred(), + promise; + // Add errorDeferred methods, then and promise + jQuery.extend( deferred, { + then: function( doneCallbacks, failCallbacks ) { + deferred.done( doneCallbacks ).fail( failCallbacks ); + return this; + }, + always: function() { + return deferred.done.apply( deferred, arguments ).fail.apply( this, arguments ); + }, + fail: failDeferred.done, + rejectWith: failDeferred.resolveWith, + reject: failDeferred.resolve, + isRejected: failDeferred.isResolved, + pipe: function( fnDone, fnFail ) { + return jQuery.Deferred(function( newDefer ) { + jQuery.each( { + done: [ fnDone, "resolve" ], + fail: [ fnFail, "reject" ] + }, function( handler, data ) { + var fn = data[ 0 ], + action = data[ 1 ], + returned; + if ( jQuery.isFunction( fn ) ) { + deferred[ handler ](function() { + returned = fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise().then( newDefer.resolve, newDefer.reject ); + } else { + newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] ); + } + }); + } else { + deferred[ handler ]( newDefer[ action ] ); + } + }); + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + if ( obj == null ) { + if ( promise ) { + return promise; + } + promise = obj = {}; + } + var i = promiseMethods.length; + while( i-- ) { + obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ]; + } + return obj; + } + }); + // Make sure only one callback list will be used + deferred.done( failDeferred.cancel ).fail( deferred.cancel ); + // Unexpose cancel + delete deferred.cancel; + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + return deferred; + }, + + // Deferred helper + when: function( firstParam ) { + var args = arguments, + i = 0, + length = args.length, + count = length, + deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? + firstParam : + jQuery.Deferred(); + function resolveFunc( i ) { + return function( value ) { + args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; + if ( !( --count ) ) { + // Strange bug in FF4: + // Values changed onto the arguments object sometimes end up as undefined values + // outside the $.when method. Cloning the object into a fresh array solves the issue + deferred.resolveWith( deferred, sliceDeferred.call( args, 0 ) ); + } + }; + } + if ( length > 1 ) { + for( ; i < length; i++ ) { + if ( args[ i ] && jQuery.isFunction( args[ i ].promise ) ) { + args[ i ].promise().then( resolveFunc(i), deferred.reject ); + } else { + --count; + } + } + if ( !count ) { + deferred.resolveWith( deferred, args ); + } + } else if ( deferred !== firstParam ) { + deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); + } + return deferred.promise(); + } +}); + + + +jQuery.support = (function() { + + var div = document.createElement( "div" ), + documentElement = document.documentElement, + all, + a, + select, + opt, + input, + marginDiv, + support, + fragment, + body, + testElementParent, + testElement, + testElementStyle, + tds, + events, + eventName, + i, + isSupported; + + // Preliminary tests + div.setAttribute("className", "t"); + div.innerHTML = "
    a"; + + + all = div.getElementsByTagName( "*" ); + a = div.getElementsByTagName( "a" )[ 0 ]; + + // Can't get basic test support + if ( !all || !all.length || !a ) { + return {}; + } + + // First batch of supports tests + select = document.createElement( "select" ); + opt = select.appendChild( document.createElement("option") ); + input = div.getElementsByTagName( "input" )[ 0 ]; + + support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: ( div.firstChild.nodeType === 3 ), + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName( "tbody" ).length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName( "link" ).length, + + // Get the style information from getAttribute + // (IE uses .cssText instead) + style: /top/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: ( a.getAttribute( "href" ) === "/a" ), + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.55$/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn: ( input.value === "on" ), + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: opt.selected, + + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + getSetAttribute: div.className !== "t", + + // Will be defined later + submitBubbles: true, + changeBubbles: true, + focusinBubbles: false, + deleteExpando: true, + noCloneEvent: true, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + reliableMarginRight: true + }; + + // Make sure checked status is properly cloned + input.checked = true; + support.noCloneChecked = input.cloneNode( true ).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + + if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { + div.attachEvent( "onclick", function() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + support.noCloneEvent = false; + }); + div.cloneNode( true ).fireEvent( "onclick" ); + } + + // Check if a radio maintains it's value + // after being appended to the DOM + input = document.createElement("input"); + input.value = "t"; + input.setAttribute("type", "radio"); + support.radioValue = input.value === "t"; + + input.setAttribute("checked", "checked"); + div.appendChild( input ); + fragment = document.createDocumentFragment(); + fragment.appendChild( div.firstChild ); + + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + + div.innerHTML = ""; + + // Figure out if the W3C box model works as expected + div.style.width = div.style.paddingLeft = "1px"; + + body = document.getElementsByTagName( "body" )[ 0 ]; + // We use our own, invisible, body unless the body is already present + // in which case we use a div (#9239) + testElement = document.createElement( body ? "div" : "body" ); + testElementStyle = { + visibility: "hidden", + width: 0, + height: 0, + border: 0, + margin: 0, + background: "none" + }; + if ( body ) { + jQuery.extend( testElementStyle, { + position: "absolute", + left: "-1000px", + top: "-1000px" + }); + } + for ( i in testElementStyle ) { + testElement.style[ i ] = testElementStyle[ i ]; + } + testElement.appendChild( div ); + testElementParent = body || documentElement; + testElementParent.insertBefore( testElement, testElementParent.firstChild ); + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + support.boxModel = div.offsetWidth === 2; + + if ( "zoom" in div.style ) { + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + // (IE < 8 does this) + div.style.display = "inline"; + div.style.zoom = 1; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 2 ); + + // Check if elements with layout shrink-wrap their children + // (IE 6 does this) + div.style.display = ""; + div.innerHTML = "
    "; + support.shrinkWrapBlocks = ( div.offsetWidth !== 2 ); + } + + div.innerHTML = "
    t
    "; + tds = div.getElementsByTagName( "td" ); + + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + // (only IE 8 fails this test) + isSupported = ( tds[ 0 ].offsetHeight === 0 ); + + tds[ 0 ].style.display = ""; + tds[ 1 ].style.display = "none"; + + // Check if empty table cells still have offsetWidth/Height + // (IE < 8 fail this test) + support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); + div.innerHTML = ""; + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. For more + // info see bug #3333 + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + if ( document.defaultView && document.defaultView.getComputedStyle ) { + marginDiv = document.createElement( "div" ); + marginDiv.style.width = "0"; + marginDiv.style.marginRight = "0"; + div.appendChild( marginDiv ); + support.reliableMarginRight = + ( parseInt( ( document.defaultView.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; + } + + // Remove the body element we added + testElement.innerHTML = ""; + testElementParent.removeChild( testElement ); + + // Technique from Juriy Zaytsev + // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ + // We only care about the case where non-standard event systems + // are used, namely in IE. Short-circuiting here helps us to + // avoid an eval call (in setAttribute) which can cause CSP + // to go haywire. See: https://developer.mozilla.org/en/Security/CSP + if ( div.attachEvent ) { + for( i in { + submit: 1, + change: 1, + focusin: 1 + } ) { + eventName = "on" + i; + isSupported = ( eventName in div ); + if ( !isSupported ) { + div.setAttribute( eventName, "return;" ); + isSupported = ( typeof div[ eventName ] === "function" ); + } + support[ i + "Bubbles" ] = isSupported; + } + } + + // Null connected elements to avoid leaks in IE + testElement = fragment = select = opt = body = marginDiv = div = input = null; + + return support; +})(); + +// Keep track of boxModel +jQuery.boxModel = jQuery.support.boxModel; + + + + +var rbrace = /^(?:\{.*\}|\[.*\])$/, + rmultiDash = /([A-Z])/g; + +jQuery.extend({ + cache: {}, + + // Please use with caution + uuid: 0, + + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, ret, + internalKey = jQuery.expando, + getByName = typeof name === "string", + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ jQuery.expando ] : elem[ jQuery.expando ] && jQuery.expando; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || (pvt && id && (cache[ id ] && !cache[ id ][ internalKey ]))) && getByName && data === undefined ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ jQuery.expando ] = id = ++jQuery.uuid; + } else { + id = jQuery.expando; + } + } + + if ( !cache[ id ] ) { + cache[ id ] = {}; + + // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery + // metadata on plain JS objects when the object is serialized using + // JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name); + } else { + cache[ id ] = jQuery.extend(cache[ id ], name); + } + } + + thisCache = cache[ id ]; + + // Internal jQuery data is stored in a separate object inside the object's data + // cache in order to avoid key collisions between internal data and user-defined + // data + if ( pvt ) { + if ( !thisCache[ internalKey ] ) { + thisCache[ internalKey ] = {}; + } + + thisCache = thisCache[ internalKey ]; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // TODO: This is a hack for 1.5 ONLY. It will be removed in 1.6. Users should + // not attempt to inspect the internal events object using jQuery.data, as this + // internal data object is undocumented and subject to change. + if ( name === "events" && !thisCache[name] ) { + return thisCache[ internalKey ] && thisCache[ internalKey ].events; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( getByName ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; + }, + + removeData: function( elem, name, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, + + // Reference to internal data cache key + internalKey = jQuery.expando, + + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + + // See jQuery.data for more information + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ][ internalKey ] : cache[ id ]; + + if ( thisCache ) { + + // Support interoperable removal of hyphenated or camelcased keys + if ( !thisCache[ name ] ) { + name = jQuery.camelCase( name ); + } + + delete thisCache[ name ]; + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !isEmptyDataObject(thisCache) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( pvt ) { + delete cache[ id ][ internalKey ]; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject(cache[ id ]) ) { + return; + } + } + + var internalCache = cache[ id ][ internalKey ]; + + // Browsers that fail expando deletion also refuse to delete expandos on + // the window, but it will allow it on all other JS objects; other browsers + // don't care + // Ensure that `cache` is not a window object #10080 + if ( jQuery.support.deleteExpando || !cache.setInterval ) { + delete cache[ id ]; + } else { + cache[ id ] = null; + } + + // We destroyed the entire user cache at once because it's faster than + // iterating through each key, but we need to continue to persist internal + // data if it existed + if ( internalCache ) { + cache[ id ] = {}; + // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery + // metadata on plain JS objects when the object is serialized using + // JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + + cache[ id ][ internalKey ] = internalCache; + + // Otherwise, we need to eliminate the expando on the node to avoid + // false lookups in the cache for entries that no longer exist + } else if ( isNode ) { + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( jQuery.support.deleteExpando ) { + delete elem[ jQuery.expando ]; + } else if ( elem.removeAttribute ) { + elem.removeAttribute( jQuery.expando ); + } else { + elem[ jQuery.expando ] = null; + } + } + }, + + // For internal use only. + _data: function( elem, name, data ) { + return jQuery.data( elem, name, data, true ); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + if ( elem.nodeName ) { + var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; + + if ( match ) { + return !(match === true || elem.getAttribute("classid") !== match); + } + } + + return true; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var data = null; + + if ( typeof key === "undefined" ) { + if ( this.length ) { + data = jQuery.data( this[0] ); + + if ( this[0].nodeType === 1 ) { + var attr = this[0].attributes, name; + for ( var i = 0, l = attr.length; i < l; i++ ) { + name = attr[i].name; + + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.substring(5) ); + + dataAttr( this[0], name, data[ name ] ); + } + } + } + } + + return data; + + } else if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + var parts = key.split("."); + parts[1] = parts[1] ? "." + parts[1] : ""; + + if ( value === undefined ) { + data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); + + // Try to fetch any internally stored data first + if ( data === undefined && this.length ) { + data = jQuery.data( this[0], key ); + data = dataAttr( this[0], key, data ); + } + + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + + } else { + return this.each(function() { + var $this = jQuery( this ), + args = [ parts[0], value ]; + + $this.triggerHandler( "setData" + parts[1] + "!", args ); + jQuery.data( this, key, value ); + $this.triggerHandler( "changeData" + parts[1] + "!", args ); + }); + } + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + !jQuery.isNaN( data ) ? parseFloat( data ) : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// TODO: This is a hack for 1.5 ONLY to allow objects with a single toJSON +// property to be considered empty objects; this property always exists in +// order to make sure JSON.stringify does not expose internal metadata +function isEmptyDataObject( obj ) { + for ( var name in obj ) { + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} + + + + +function handleQueueMarkDefer( elem, type, src ) { + var deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + defer = jQuery.data( elem, deferDataKey, undefined, true ); + if ( defer && + ( src === "queue" || !jQuery.data( elem, queueDataKey, undefined, true ) ) && + ( src === "mark" || !jQuery.data( elem, markDataKey, undefined, true ) ) ) { + // Give room for hard-coded callbacks to fire first + // and eventually mark/queue something else on the element + setTimeout( function() { + if ( !jQuery.data( elem, queueDataKey, undefined, true ) && + !jQuery.data( elem, markDataKey, undefined, true ) ) { + jQuery.removeData( elem, deferDataKey, true ); + defer.resolve(); + } + }, 0 ); + } +} + +jQuery.extend({ + + _mark: function( elem, type ) { + if ( elem ) { + type = (type || "fx") + "mark"; + jQuery.data( elem, type, (jQuery.data(elem,type,undefined,true) || 0) + 1, true ); + } + }, + + _unmark: function( force, elem, type ) { + if ( force !== true ) { + type = elem; + elem = force; + force = false; + } + if ( elem ) { + type = type || "fx"; + var key = type + "mark", + count = force ? 0 : ( (jQuery.data( elem, key, undefined, true) || 1 ) - 1 ); + if ( count ) { + jQuery.data( elem, key, count, true ); + } else { + jQuery.removeData( elem, key, true ); + handleQueueMarkDefer( elem, type, "mark" ); + } + } + }, + + queue: function( elem, type, data ) { + if ( elem ) { + type = (type || "fx") + "queue"; + var q = jQuery.data( elem, type, undefined, true ); + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !q || jQuery.isArray(data) ) { + q = jQuery.data( elem, type, jQuery.makeArray(data), true ); + } else { + q.push( data ); + } + } + return q || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + fn = queue.shift(), + defer; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + } + + if ( fn ) { + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift("inprogress"); + } + + fn.call(elem, function() { + jQuery.dequeue(elem, type); + }); + } + + if ( !queue.length ) { + jQuery.removeData( elem, type + "queue", true ); + handleQueueMarkDefer( elem, type, "queue" ); + } + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + } + + if ( data === undefined ) { + return jQuery.queue( this[0], type ); + } + return this.each(function() { + var queue = jQuery.queue( this, type, data ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; + type = type || "fx"; + + return this.queue( type, function() { + var elem = this; + setTimeout(function() { + jQuery.dequeue( elem, type ); + }, time ); + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, object ) { + if ( typeof type !== "string" ) { + object = type; + type = undefined; + } + type = type || "fx"; + var defer = jQuery.Deferred(), + elements = this, + i = elements.length, + count = 1, + deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + tmp; + function resolve() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + } + while( i-- ) { + if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) || + ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) || + jQuery.data( elements[ i ], markDataKey, undefined, true ) ) && + jQuery.data( elements[ i ], deferDataKey, jQuery._Deferred(), true ) )) { + count++; + tmp.done( resolve ); + } + } + resolve(); + return defer.promise(); + } +}); + + + + +var rclass = /[\n\t\r]/g, + rspace = /\s+/, + rreturn = /\r/g, + rtype = /^(?:button|input)$/i, + rfocusable = /^(?:button|input|object|select|textarea)$/i, + rclickable = /^a(?:rea)?$/i, + rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, + nodeHook, boolHook; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, name, value, true, jQuery.attr ); + }, + + removeAttr: function( name ) { + return this.each(function() { + jQuery.removeAttr( this, name ); + }); + }, + + prop: function( name, value ) { + return jQuery.access( this, name, value, true, jQuery.prop ); + }, + + removeProp: function( name ) { + name = jQuery.propFix[ name ] || name; + return this.each(function() { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch( e ) {} + }); + }, + + addClass: function( value ) { + var classNames, i, l, elem, + setClass, c, cl; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).addClass( value.call(this, j, this.className) ); + }); + } + + if ( value && typeof value === "string" ) { + classNames = value.split( rspace ); + + for ( i = 0, l = this.length; i < l; i++ ) { + elem = this[ i ]; + + if ( elem.nodeType === 1 ) { + if ( !elem.className && classNames.length === 1 ) { + elem.className = value; + + } else { + setClass = " " + elem.className + " "; + + for ( c = 0, cl = classNames.length; c < cl; c++ ) { + if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) { + setClass += classNames[ c ] + " "; + } + } + elem.className = jQuery.trim( setClass ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classNames, i, l, elem, className, c, cl; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).removeClass( value.call(this, j, this.className) ); + }); + } + + if ( (value && typeof value === "string") || value === undefined ) { + classNames = (value || "").split( rspace ); + + for ( i = 0, l = this.length; i < l; i++ ) { + elem = this[ i ]; + + if ( elem.nodeType === 1 && elem.className ) { + if ( value ) { + className = (" " + elem.className + " ").replace( rclass, " " ); + for ( c = 0, cl = classNames.length; c < cl; c++ ) { + className = className.replace(" " + classNames[ c ] + " ", " "); + } + elem.className = jQuery.trim( className ); + + } else { + elem.className = ""; + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( i ) { + jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + state = stateVal, + classNames = value.split( rspace ); + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space seperated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + } else if ( type === "undefined" || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery._data( this, "__className__", this.className ); + } + + // toggle whole className + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " "; + for ( var i = 0, l = this.length; i < l; i++ ) { + if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + var hooks, ret, + elem = this[0]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ]; + + if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { + return ret; + } + + ret = elem.value; + + return typeof ret === "string" ? + // handle most common string cases + ret.replace(rreturn, "") : + // handle cases where value is null/undef or number + ret == null ? "" : ret; + } + + return undefined; + } + + var isFunction = jQuery.isFunction( value ); + + return this.each(function( i ) { + var self = jQuery(this), val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, self.val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map(val, function ( value ) { + return value == null ? "" : value + ""; + }); + } + + hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + valHooks: { + option: { + get: function( elem ) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + }, + select: { + get: function( elem ) { + var value, + index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if ( index < 0 ) { + return null; + } + + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[ i ]; + + // Don't return options that are disabled or in a disabled optgroup + if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && + (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + // Fixes Bug #2551 -- select.val() broken in IE after form.reset() + if ( one && !values.length && options.length ) { + return jQuery( options[ index ] ).val(); + } + + return values; + }, + + set: function( elem, value ) { + var values = jQuery.makeArray( value ); + + jQuery(elem).find("option").each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + elem.selectedIndex = -1; + } + return values; + } + } + }, + + attrFn: { + val: true, + css: true, + html: true, + text: true, + data: true, + width: true, + height: true, + offset: true + }, + + attrFix: { + // Always normalize to ensure hook usage + tabindex: "tabIndex" + }, + + attr: function( elem, name, value, pass ) { + var nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return undefined; + } + + if ( pass && name in jQuery.attrFn ) { + return jQuery( elem )[ name ]( value ); + } + + // Fallback to prop when attributes are not supported + if ( !("getAttribute" in elem) ) { + return jQuery.prop( elem, name, value ); + } + + var ret, hooks, + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + // Normalize the name if needed + if ( notxml ) { + name = jQuery.attrFix[ name ] || name; + + hooks = jQuery.attrHooks[ name ]; + + if ( !hooks ) { + // Use boolHook for boolean attributes + if ( rboolean.test( name ) ) { + hooks = boolHook; + + // Use nodeHook if available( IE6/7 ) + } else if ( nodeHook ) { + hooks = nodeHook; + } + } + } + + if ( value !== undefined ) { + + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return undefined; + + } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + elem.setAttribute( name, "" + value ); + return value; + } + + } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + + ret = elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return ret === null ? + undefined : + ret; + } + }, + + removeAttr: function( elem, name ) { + var propName; + if ( elem.nodeType === 1 ) { + name = jQuery.attrFix[ name ] || name; + + jQuery.attr( elem, name, "" ); + elem.removeAttribute( name ); + + // Set corresponding property to false for boolean attributes + if ( rboolean.test( name ) && (propName = jQuery.propFix[ name ] || name) in elem ) { + elem[ propName ] = false; + } + } + }, + + attrHooks: { + type: { + set: function( elem, value ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); + } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to it's default in case type is set after value + // This is for element creation + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + }, + // Use the value property for back compat + // Use the nodeHook for button elements in IE6/7 (#1954) + value: { + get: function( elem, name ) { + if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { + return nodeHook.get( elem, name ); + } + return name in elem ? + elem.value : + null; + }, + set: function( elem, value, name ) { + if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { + return nodeHook.set( elem, value, name ); + } + // Does not return so that setAttribute is also used + elem.value = value; + } + } + }, + + propFix: { + tabindex: "tabIndex", + readonly: "readOnly", + "for": "htmlFor", + "class": "className", + maxlength: "maxLength", + cellspacing: "cellSpacing", + cellpadding: "cellPadding", + rowspan: "rowSpan", + colspan: "colSpan", + usemap: "useMap", + frameborder: "frameBorder", + contenteditable: "contentEditable" + }, + + prop: function( elem, name, value ) { + var nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return undefined; + } + + var ret, hooks, + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + if ( notxml ) { + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + return (elem[ name ] = value); + } + + } else { + if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + return elem[ name ]; + } + } + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + var attributeNode = elem.getAttributeNode("tabindex"); + + return attributeNode && attributeNode.specified ? + parseInt( attributeNode.value, 10 ) : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + } + } +}); + +// Add the tabindex propHook to attrHooks for back-compat +jQuery.attrHooks.tabIndex = jQuery.propHooks.tabIndex; + +// Hook for boolean attributes +boolHook = { + get: function( elem, name ) { + // Align boolean attributes with corresponding properties + // Fall back to attribute presence where some booleans are not supported + var attrNode; + return jQuery.prop( elem, name ) === true || ( attrNode = elem.getAttributeNode( name ) ) && attrNode.nodeValue !== false ? + name.toLowerCase() : + undefined; + }, + set: function( elem, value, name ) { + var propName; + if ( value === false ) { + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + // value is true since we know at this point it's type boolean and not false + // Set boolean attributes to the same name and set the DOM property + propName = jQuery.propFix[ name ] || name; + if ( propName in elem ) { + // Only set the IDL specifically if it already exists on the element + elem[ propName ] = true; + } + + elem.setAttribute( name, name.toLowerCase() ); + } + return name; + } +}; + +// IE6/7 do not support getting/setting some attributes with get/setAttribute +if ( !jQuery.support.getSetAttribute ) { + + // Use this for any attribute in IE6/7 + // This fixes almost every IE6/7 issue + nodeHook = jQuery.valHooks.button = { + get: function( elem, name ) { + var ret; + ret = elem.getAttributeNode( name ); + // Return undefined if nodeValue is empty string + return ret && ret.nodeValue !== "" ? + ret.nodeValue : + undefined; + }, + set: function( elem, value, name ) { + // Set the existing or create a new attribute node + var ret = elem.getAttributeNode( name ); + if ( !ret ) { + ret = document.createAttribute( name ); + elem.setAttributeNode( ret ); + } + return (ret.nodeValue = value + ""); + } + }; + + // Set width and height to auto instead of 0 on empty string( Bug #8150 ) + // This is for removals + jQuery.each([ "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + set: function( elem, value ) { + if ( value === "" ) { + elem.setAttribute( name, "auto" ); + return value; + } + } + }); + }); +} + + +// Some attributes require a special call on IE +if ( !jQuery.support.hrefNormalized ) { + jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + get: function( elem ) { + var ret = elem.getAttribute( name, 2 ); + return ret === null ? undefined : ret; + } + }); + }); +} + +if ( !jQuery.support.style ) { + jQuery.attrHooks.style = { + get: function( elem ) { + // Return undefined in the case of empty string + // Normalize to lowercase since IE uppercases css property names + return elem.style.cssText.toLowerCase() || undefined; + }, + set: function( elem, value ) { + return (elem.style.cssText = "" + value); + } + }; +} + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it +if ( !jQuery.support.optSelected ) { + jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { + get: function( elem ) { + var parent = elem.parentNode; + + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + return null; + } + }); +} + +// Radios and checkboxes getter/setter +if ( !jQuery.support.checkOn ) { + jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + get: function( elem ) { + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + } + }; + }); +} +jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return (elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0); + } + } + }); +}); + + + + +var rnamespaces = /\.(.*)$/, + rformElems = /^(?:textarea|input|select)$/i, + rperiod = /\./g, + rspaces = / /g, + rescape = /[^\w\s.|`]/g, + fcleanup = function( nm ) { + return nm.replace(rescape, "\\$&"); + }; + +/* + * A number of helper functions used for managing events. + * Many of the ideas behind this code originated from + * Dean Edwards' addEvent library. + */ +jQuery.event = { + + // Bind an event to an element + // Original by Dean Edwards + add: function( elem, types, handler, data ) { + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + if ( handler === false ) { + handler = returnFalse; + } else if ( !handler ) { + // Fixes bug #7229. Fix recommended by jdalton + return; + } + + var handleObjIn, handleObj; + + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + } + + // Make sure that the function being executed has a unique ID + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure + var elemData = jQuery._data( elem ); + + // If no elemData is found then we must be trying to bind to one of the + // banned noData elements + if ( !elemData ) { + return; + } + + var events = elemData.events, + eventHandle = elemData.handle; + + if ( !events ) { + elemData.events = events = {}; + } + + if ( !eventHandle ) { + elemData.handle = eventHandle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.handle.apply( eventHandle.elem, arguments ) : + undefined; + }; + } + + // Add elem as a property of the handle function + // This is to prevent a memory leak with non-native events in IE. + eventHandle.elem = elem; + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = types.split(" "); + + var type, i = 0, namespaces; + + while ( (type = types[ i++ ]) ) { + handleObj = handleObjIn ? + jQuery.extend({}, handleObjIn) : + { handler: handler, data: data }; + + // Namespaced event handlers + if ( type.indexOf(".") > -1 ) { + namespaces = type.split("."); + type = namespaces.shift(); + handleObj.namespace = namespaces.slice(0).sort().join("."); + + } else { + namespaces = []; + handleObj.namespace = ""; + } + + handleObj.type = type; + if ( !handleObj.guid ) { + handleObj.guid = handler.guid; + } + + // Get the current list of functions bound to this event + var handlers = events[ type ], + special = jQuery.event.special[ type ] || {}; + + // Init the event handler queue + if ( !handlers ) { + handlers = events[ type ] = []; + + // Check for a special event handler + // Only use addEventListener/attachEvent if the special + // events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add the function to the element's handler list + handlers.push( handleObj ); + + // Keep track of which events have been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global: {}, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, pos ) { + // don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + if ( handler === false ) { + handler = returnFalse; + } + + var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, + elemData = jQuery.hasData( elem ) && jQuery._data( elem ), + events = elemData && elemData.events; + + if ( !elemData || !events ) { + return; + } + + // types is actually an event object here + if ( types && types.type ) { + handler = types.handler; + types = types.type; + } + + // Unbind all events for the element + if ( !types || typeof types === "string" && types.charAt(0) === "." ) { + types = types || ""; + + for ( type in events ) { + jQuery.event.remove( elem, type + types ); + } + + return; + } + + // Handle multiple events separated by a space + // jQuery(...).unbind("mouseover mouseout", fn); + types = types.split(" "); + + while ( (type = types[ i++ ]) ) { + origType = type; + handleObj = null; + all = type.indexOf(".") < 0; + namespaces = []; + + if ( !all ) { + // Namespaced event handlers + namespaces = type.split("."); + type = namespaces.shift(); + + namespace = new RegExp("(^|\\.)" + + jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + eventType = events[ type ]; + + if ( !eventType ) { + continue; + } + + if ( !handler ) { + for ( j = 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( all || namespace.test( handleObj.namespace ) ) { + jQuery.event.remove( elem, origType, handleObj.handler, j ); + eventType.splice( j--, 1 ); + } + } + + continue; + } + + special = jQuery.event.special[ type ] || {}; + + for ( j = pos || 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( handler.guid === handleObj.guid ) { + // remove the given handler for the given type + if ( all || namespace.test( handleObj.namespace ) ) { + if ( pos == null ) { + eventType.splice( j--, 1 ); + } + + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + + if ( pos != null ) { + break; + } + } + } + + // remove generic event handler if no more handlers exist + if ( eventType.length === 0 || pos != null && eventType.length === 1 ) { + if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + ret = null; + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + var handle = elemData.handle; + if ( handle ) { + handle.elem = null; + } + + delete elemData.events; + delete elemData.handle; + + if ( jQuery.isEmptyObject( elemData ) ) { + jQuery.removeData( elem, undefined, true ); + } + } + }, + + // Events that are safe to short-circuit if no handlers are attached. + // Native DOM events should not be added, they may have inline handlers. + customEvent: { + "getData": true, + "setData": true, + "changeData": true + }, + + trigger: function( event, data, elem, onlyHandlers ) { + // Event object or event type + var type = event.type || event, + namespaces = [], + exclusive; + + if ( type.indexOf("!") >= 0 ) { + // Exclusive events trigger only for the exact event (no namespaces) + type = type.slice(0, -1); + exclusive = true; + } + + if ( type.indexOf(".") >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + + if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) { + // No jQuery handlers for this event type, and it can't have inline handlers + return; + } + + // Caller can pass in an Event, Object, or just an event type string + event = typeof event === "object" ? + // jQuery.Event object + event[ jQuery.expando ] ? event : + // Object literal + new jQuery.Event( type, event ) : + // Just the event type (string) + new jQuery.Event( type ); + + event.type = type; + event.exclusive = exclusive; + event.namespace = namespaces.join("."); + event.namespace_re = new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)"); + + // triggerHandler() and global events don't bubble or run the default action + if ( onlyHandlers || !elem ) { + event.preventDefault(); + event.stopPropagation(); + } + + // Handle a global trigger + if ( !elem ) { + // TODO: Stop taunting the data cache; remove global events and always attach to document + jQuery.each( jQuery.cache, function() { + // internalKey variable is just used to make it easier to find + // and potentially change this stuff later; currently it just + // points to jQuery.expando + var internalKey = jQuery.expando, + internalCache = this[ internalKey ]; + if ( internalCache && internalCache.events && internalCache.events[ type ] ) { + jQuery.event.trigger( event, data, internalCache.handle.elem ); + } + }); + return; + } + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // Clean up the event in case it is being reused + event.result = undefined; + event.target = elem; + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data != null ? jQuery.makeArray( data ) : []; + data.unshift( event ); + + var cur = elem, + // IE doesn't like method names with a colon (#3533, #8272) + ontype = type.indexOf(":") < 0 ? "on" + type : ""; + + // Fire event on the current element, then bubble up the DOM tree + do { + var handle = jQuery._data( cur, "handle" ); + + event.currentTarget = cur; + if ( handle ) { + handle.apply( cur, data ); + } + + // Trigger an inline bound script + if ( ontype && jQuery.acceptData( cur ) && cur[ ontype ] && cur[ ontype ].apply( cur, data ) === false ) { + event.result = false; + event.preventDefault(); + } + + // Bubble up to document, then to window + cur = cur.parentNode || cur.ownerDocument || cur === event.target.ownerDocument && window; + } while ( cur && !event.isPropagationStopped() ); + + // If nobody prevented the default action, do it now + if ( !event.isDefaultPrevented() ) { + var old, + special = jQuery.event.special[ type ] || {}; + + if ( (!special._default || special._default.call( elem.ownerDocument, event ) === false) && + !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction)() check here because IE6/7 fails that test. + // IE<9 dies on focus to hidden element (#1486), may want to revisit a try/catch. + try { + if ( ontype && elem[ type ] ) { + // Don't re-trigger an onFOO event when we call its FOO() method + old = elem[ ontype ]; + + if ( old ) { + elem[ ontype ] = null; + } + + jQuery.event.triggered = type; + elem[ type ](); + } + } catch ( ieError ) {} + + if ( old ) { + elem[ ontype ] = old; + } + + jQuery.event.triggered = undefined; + } + } + + return event.result; + }, + + handle: function( event ) { + event = jQuery.event.fix( event || window.event ); + // Snapshot the handlers list since a called handler may add/remove events. + var handlers = ((jQuery._data( this, "events" ) || {})[ event.type ] || []).slice(0), + run_all = !event.exclusive && !event.namespace, + args = Array.prototype.slice.call( arguments, 0 ); + + // Use the fix-ed Event rather than the (read-only) native event + args[0] = event; + event.currentTarget = this; + + for ( var j = 0, l = handlers.length; j < l; j++ ) { + var handleObj = handlers[ j ]; + + // Triggered event must 1) be non-exclusive and have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event. + if ( run_all || event.namespace_re.test( handleObj.namespace ) ) { + // Pass in a reference to the handler function itself + // So that we can later remove it + event.handler = handleObj.handler; + event.data = handleObj.data; + event.handleObj = handleObj; + + var ret = handleObj.handler.apply( this, args ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + + if ( event.isImmediatePropagationStopped() ) { + break; + } + } + } + return event.result; + }, + + props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // store a copy of the original event object + // and "clone" to set read-only properties + var originalEvent = event; + event = jQuery.Event( originalEvent ); + + for ( var i = this.props.length, prop; i; ) { + prop = this.props[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary + if ( !event.target ) { + // Fixes #1925 where srcElement might not be defined either + event.target = event.srcElement || document; + } + + // check if target is a textnode (safari) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && event.fromElement ) { + event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; + } + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && event.clientX != null ) { + var eventDocument = event.target.ownerDocument || document, + doc = eventDocument.documentElement, + body = eventDocument.body; + + event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); + event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); + } + + // Add which for key events + if ( event.which == null && (event.charCode != null || event.keyCode != null) ) { + event.which = event.charCode != null ? event.charCode : event.keyCode; + } + + // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) + if ( !event.metaKey && event.ctrlKey ) { + event.metaKey = event.ctrlKey; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && event.button !== undefined ) { + event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); + } + + return event; + }, + + // Deprecated, use jQuery.guid instead + guid: 1E8, + + // Deprecated, use jQuery.proxy instead + proxy: jQuery.proxy, + + special: { + ready: { + // Make sure the ready event is setup + setup: jQuery.bindReady, + teardown: jQuery.noop + }, + + live: { + add: function( handleObj ) { + jQuery.event.add( this, + liveConvert( handleObj.origType, handleObj.selector ), + jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) ); + }, + + remove: function( handleObj ) { + jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj ); + } + }, + + beforeunload: { + setup: function( data, namespaces, eventHandle ) { + // We only want to do this special case on windows + if ( jQuery.isWindow( this ) ) { + this.onbeforeunload = eventHandle; + } + }, + + teardown: function( namespaces, eventHandle ) { + if ( this.onbeforeunload === eventHandle ) { + this.onbeforeunload = null; + } + } + } + } +}; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + if ( elem.detachEvent ) { + elem.detachEvent( "on" + type, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !this.preventDefault ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // timeStamp is buggy for some events on Firefox(#3843) + // So we won't rely on the native value + this.timeStamp = jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +function returnFalse() { + return false; +} +function returnTrue() { + return true; +} + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + preventDefault: function() { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + + // if preventDefault exists run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // otherwise set the returnValue property of the original event to false (IE) + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + // if stopPropagation exists run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse +}; + +// Checks if an event happened on an element within another element +// Used in jQuery.event.special.mouseenter and mouseleave handlers +var withinElement = function( event ) { + + // Check if mouse(over|out) are still within the same parent element + var related = event.relatedTarget, + inside = false, + eventType = event.type; + + event.type = event.data; + + if ( related !== this ) { + + if ( related ) { + inside = jQuery.contains( this, related ); + } + + if ( !inside ) { + + jQuery.event.handle.apply( this, arguments ); + + event.type = eventType; + } + } +}, + +// In case of event delegation, we only need to rename the event.type, +// liveHandler will take care of the rest. +delegate = function( event ) { + event.type = event.data; + jQuery.event.handle.apply( this, arguments ); +}; + +// Create mouseenter and mouseleave events +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + setup: function( data ) { + jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig ); + }, + teardown: function( data ) { + jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement ); + } + }; +}); + +// submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function( data, namespaces ) { + if ( !jQuery.nodeName( this, "form" ) ) { + jQuery.event.add(this, "click.specialSubmit", function( e ) { + // Avoid triggering error on non-existent type attribute in IE VML (#7071) + var elem = e.target, + type = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.type : ""; + + if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { + trigger( "submit", this, arguments ); + } + }); + + jQuery.event.add(this, "keypress.specialSubmit", function( e ) { + var elem = e.target, + type = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.type : ""; + + if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { + trigger( "submit", this, arguments ); + } + }); + + } else { + return false; + } + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialSubmit" ); + } + }; + +} + +// change delegation, happens here so we have bind. +if ( !jQuery.support.changeBubbles ) { + + var changeFilters, + + getVal = function( elem ) { + var type = jQuery.nodeName( elem, "input" ) ? elem.type : "", + val = elem.value; + + if ( type === "radio" || type === "checkbox" ) { + val = elem.checked; + + } else if ( type === "select-multiple" ) { + val = elem.selectedIndex > -1 ? + jQuery.map( elem.options, function( elem ) { + return elem.selected; + }).join("-") : + ""; + + } else if ( jQuery.nodeName( elem, "select" ) ) { + val = elem.selectedIndex; + } + + return val; + }, + + testChange = function testChange( e ) { + var elem = e.target, data, val; + + if ( !rformElems.test( elem.nodeName ) || elem.readOnly ) { + return; + } + + data = jQuery._data( elem, "_change_data" ); + val = getVal(elem); + + // the current data will be also retrieved by beforeactivate + if ( e.type !== "focusout" || elem.type !== "radio" ) { + jQuery._data( elem, "_change_data", val ); + } + + if ( data === undefined || val === data ) { + return; + } + + if ( data != null || val ) { + e.type = "change"; + e.liveFired = undefined; + jQuery.event.trigger( e, arguments[1], elem ); + } + }; + + jQuery.event.special.change = { + filters: { + focusout: testChange, + + beforedeactivate: testChange, + + click: function( e ) { + var elem = e.target, type = jQuery.nodeName( elem, "input" ) ? elem.type : ""; + + if ( type === "radio" || type === "checkbox" || jQuery.nodeName( elem, "select" ) ) { + testChange.call( this, e ); + } + }, + + // Change has to be called before submit + // Keydown will be called before keypress, which is used in submit-event delegation + keydown: function( e ) { + var elem = e.target, type = jQuery.nodeName( elem, "input" ) ? elem.type : ""; + + if ( (e.keyCode === 13 && !jQuery.nodeName( elem, "textarea" ) ) || + (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || + type === "select-multiple" ) { + testChange.call( this, e ); + } + }, + + // Beforeactivate happens also before the previous element is blurred + // with this event you can't trigger a change event, but you can store + // information + beforeactivate: function( e ) { + var elem = e.target; + jQuery._data( elem, "_change_data", getVal(elem) ); + } + }, + + setup: function( data, namespaces ) { + if ( this.type === "file" ) { + return false; + } + + for ( var type in changeFilters ) { + jQuery.event.add( this, type + ".specialChange", changeFilters[type] ); + } + + return rformElems.test( this.nodeName ); + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialChange" ); + + return rformElems.test( this.nodeName ); + } + }; + + changeFilters = jQuery.event.special.change.filters; + + // Handle when the input is .focus()'d + changeFilters.focus = changeFilters.beforeactivate; +} + +function trigger( type, elem, args ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + // Don't pass args or remember liveFired; they apply to the donor event. + var event = jQuery.extend( {}, args[ 0 ] ); + event.type = type; + event.originalEvent = {}; + event.liveFired = undefined; + jQuery.event.handle.call( elem, event ); + if ( event.isDefaultPrevented() ) { + args[ 0 ].preventDefault(); + } +} + +// Create "bubbling" focus and blur events +if ( !jQuery.support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0; + + jQuery.event.special[ fix ] = { + setup: function() { + if ( attaches++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --attaches === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + + function handler( donor ) { + // Donor event is always a native one; fix it and switch its type. + // Let focusin/out handler cancel the donor focus/blur event. + var e = jQuery.event.fix( donor ); + e.type = fix; + e.originalEvent = {}; + jQuery.event.trigger( e, null, e.target ); + if ( e.isDefaultPrevented() ) { + donor.preventDefault(); + } + } + }); +} + +jQuery.each(["bind", "one"], function( i, name ) { + jQuery.fn[ name ] = function( type, data, fn ) { + var handler; + + // Handle object literals + if ( typeof type === "object" ) { + for ( var key in type ) { + this[ name ](key, data, type[key], fn); + } + return this; + } + + if ( arguments.length === 2 || data === false ) { + fn = data; + data = undefined; + } + + if ( name === "one" ) { + handler = function( event ) { + jQuery( this ).unbind( event, handler ); + return fn.apply( this, arguments ); + }; + handler.guid = fn.guid || jQuery.guid++; + } else { + handler = fn; + } + + if ( type === "unload" && name !== "one" ) { + this.one( type, data, fn ); + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.add( this[i], type, handler, data ); + } + } + + return this; + }; +}); + +jQuery.fn.extend({ + unbind: function( type, fn ) { + // Handle object literals + if ( typeof type === "object" && !type.preventDefault ) { + for ( var key in type ) { + this.unbind(key, type[key]); + } + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.remove( this[i], type, fn ); + } + } + + return this; + }, + + delegate: function( selector, types, data, fn ) { + return this.live( types, data, fn, selector ); + }, + + undelegate: function( selector, types, fn ) { + if ( arguments.length === 0 ) { + return this.unbind( "live" ); + + } else { + return this.die( types, null, fn, selector ); + } + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + + triggerHandler: function( type, data ) { + if ( this[0] ) { + return jQuery.event.trigger( type, data, this[0], true ); + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, + guid = fn.guid || jQuery.guid++, + i = 0, + toggler = function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + }; + + // link all the functions, so any of them can unbind this click handler + toggler.guid = guid; + while ( i < args.length ) { + args[ i++ ].guid = guid; + } + + return this.click( toggler ); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +var liveMap = { + focus: "focusin", + blur: "focusout", + mouseenter: "mouseover", + mouseleave: "mouseout" +}; + +jQuery.each(["live", "die"], function( i, name ) { + jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) { + var type, i = 0, match, namespaces, preType, + selector = origSelector || this.selector, + context = origSelector ? this : jQuery( this.context ); + + if ( typeof types === "object" && !types.preventDefault ) { + for ( var key in types ) { + context[ name ]( key, data, types[key], selector ); + } + + return this; + } + + if ( name === "die" && !types && + origSelector && origSelector.charAt(0) === "." ) { + + context.unbind( origSelector ); + + return this; + } + + if ( data === false || jQuery.isFunction( data ) ) { + fn = data || returnFalse; + data = undefined; + } + + types = (types || "").split(" "); + + while ( (type = types[ i++ ]) != null ) { + match = rnamespaces.exec( type ); + namespaces = ""; + + if ( match ) { + namespaces = match[0]; + type = type.replace( rnamespaces, "" ); + } + + if ( type === "hover" ) { + types.push( "mouseenter" + namespaces, "mouseleave" + namespaces ); + continue; + } + + preType = type; + + if ( liveMap[ type ] ) { + types.push( liveMap[ type ] + namespaces ); + type = type + namespaces; + + } else { + type = (liveMap[ type ] || type) + namespaces; + } + + if ( name === "live" ) { + // bind live handler + for ( var j = 0, l = context.length; j < l; j++ ) { + jQuery.event.add( context[j], "live." + liveConvert( type, selector ), + { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } ); + } + + } else { + // unbind live handler + context.unbind( "live." + liveConvert( type, selector ), fn ); + } + } + + return this; + }; +}); + +function liveHandler( event ) { + var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret, + elems = [], + selectors = [], + events = jQuery._data( this, "events" ); + + // Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911) + if ( event.liveFired === this || !events || !events.live || event.target.disabled || event.button && event.type === "click" ) { + return; + } + + if ( event.namespace ) { + namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + event.liveFired = this; + + var live = events.live.slice(0); + + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) { + selectors.push( handleObj.selector ); + + } else { + live.splice( j--, 1 ); + } + } + + match = jQuery( event.target ).closest( selectors, event.currentTarget ); + + for ( i = 0, l = match.length; i < l; i++ ) { + close = match[i]; + + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) && !close.elem.disabled ) { + elem = close.elem; + related = null; + + // Those two events require additional checking + if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) { + event.type = handleObj.preType; + related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0]; + + // Make sure not to accidentally match a child element with the same selector + if ( related && jQuery.contains( elem, related ) ) { + related = elem; + } + } + + if ( !related || related !== elem ) { + elems.push({ elem: elem, handleObj: handleObj, level: close.level }); + } + } + } + } + + for ( i = 0, l = elems.length; i < l; i++ ) { + match = elems[i]; + + if ( maxLevel && match.level > maxLevel ) { + break; + } + + event.currentTarget = match.elem; + event.data = match.handleObj.data; + event.handleObj = match.handleObj; + + ret = match.handleObj.origHandler.apply( match.elem, arguments ); + + if ( ret === false || event.isPropagationStopped() ) { + maxLevel = match.level; + + if ( ret === false ) { + stop = false; + } + if ( event.isImmediatePropagationStopped() ) { + break; + } + } + } + + return stop; +} + +function liveConvert( type, selector ) { + return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspaces, "&"); +} + +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + if ( fn == null ) { + fn = data; + data = null; + } + + return arguments.length > 0 ? + this.bind( name, data, fn ) : + this.trigger( name ); + }; + + if ( jQuery.attrFn ) { + jQuery.attrFn[ name ] = true; + } +}); + + + +/*! + * Sizzle CSS Selector Engine + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true, + rBackslash = /\\/g, + rNonWord = /\W/; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function() { + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function( selector, context, results, seed ) { + results = results || []; + context = context || document; + + var origContext = context; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var m, set, checkSet, extra, ret, cur, pop, i, + prune = true, + contextXML = Sizzle.isXML( context ), + parts = [], + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + do { + chunker.exec( "" ); + m = chunker.exec( soFar ); + + if ( m ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + } while ( m ); + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) { + selector += parts.shift(); + } + + set = posProcess( selector, set ); + } + } + + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + + ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? + Sizzle.filter( ret.expr, ret.set )[0] : + ret.set[0]; + } + + if ( context ) { + ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + + set = ret.expr ? + Sizzle.filter( ret.expr, ret.set ) : + ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray( set ); + + } else { + prune = false; + } + + while ( parts.length ) { + cur = parts.pop(); + pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + Sizzle.error( cur || selector ); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + + } else if ( context && context.nodeType === 1 ) { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + + } else { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function( results ) { + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort( sortOrder ); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[ i - 1 ] ) { + results.splice( i--, 1 ); + } + } + } + } + + return results; +}; + +Sizzle.matches = function( expr, set ) { + return Sizzle( expr, null, null, set ); +}; + +Sizzle.matchesSelector = function( node, expr ) { + return Sizzle( expr, null, null, [node] ).length > 0; +}; + +Sizzle.find = function( expr, context, isXML ) { + var set; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var match, + type = Expr.order[i]; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice( 1, 1 ); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace( rBackslash, "" ); + set = Expr.find[ type ]( match, context, isXML ); + + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = typeof context.getElementsByTagName !== "undefined" ? + context.getElementsByTagName( "*" ) : + []; + } + + return { set: set, expr: expr }; +}; + +Sizzle.filter = function( expr, set, inplace, not ) { + var match, anyFound, + old = expr, + result = [], + curLoop = set, + isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + var found, item, + filter = Expr.filter[ type ], + left = match[1]; + + anyFound = false; + + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + + } else { + curLoop[i] = false; + } + + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + Sizzle.error( expr ); + + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +Sizzle.error = function( msg ) { + throw "Syntax error, unrecognized expression: " + msg; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + + match: { + ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + + leftMatch: {}, + + attrMap: { + "class": "className", + "for": "htmlFor" + }, + + attrHandle: { + href: function( elem ) { + return elem.getAttribute( "href" ); + }, + type: function( elem ) { + return elem.getAttribute( "type" ); + } + }, + + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !rNonWord.test( part ), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag ) { + part = part.toLowerCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + + ">": function( checkSet, part ) { + var elem, + isPartStr = typeof part === "string", + i = 0, + l = checkSet.length; + + if ( isPartStr && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + + } else { + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + + "": function(checkSet, part, isXML){ + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); + }, + + "~": function( checkSet, part, isXML ) { + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); + } + }, + + find: { + ID: function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }, + + NAME: function( match, context ) { + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], + results = context.getElementsByName( match[1] ); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + + TAG: function( match, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( match[1] ); + } + } + }, + preFilter: { + CLASS: function( match, curLoop, inplace, result, not, isXML ) { + match = " " + match[1].replace( rBackslash, "" ) + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + + ID: function( match ) { + return match[1].replace( rBackslash, "" ); + }, + + TAG: function( match, curLoop ) { + return match[1].replace( rBackslash, "" ).toLowerCase(); + }, + + CHILD: function( match ) { + if ( match[1] === "nth" ) { + if ( !match[2] ) { + Sizzle.error( match[0] ); + } + + match[2] = match[2].replace(/^\+|\s*/g, ''); + + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + else if ( match[2] ) { + Sizzle.error( match[0] ); + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + + ATTR: function( match, curLoop, inplace, result, not, isXML ) { + var name = match[1] = match[1].replace( rBackslash, "" ); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + // Handle if an un-quoted value was used + match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + + PSEUDO: function( match, curLoop, inplace, result, not ) { + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + + if ( !inplace ) { + result.push.apply( result, ret ); + } + + return false; + } + + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + + POS: function( match ) { + match.unshift( true ); + + return match; + } + }, + + filters: { + enabled: function( elem ) { + return elem.disabled === false && elem.type !== "hidden"; + }, + + disabled: function( elem ) { + return elem.disabled === true; + }, + + checked: function( elem ) { + return elem.checked === true; + }, + + selected: function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + parent: function( elem ) { + return !!elem.firstChild; + }, + + empty: function( elem ) { + return !elem.firstChild; + }, + + has: function( elem, i, match ) { + return !!Sizzle( match[3], elem ).length; + }, + + header: function( elem ) { + return (/h\d/i).test( elem.nodeName ); + }, + + text: function( elem ) { + var attr = elem.getAttribute( "type" ), type = elem.type; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); + }, + + radio: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; + }, + + checkbox: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; + }, + + file: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; + }, + + password: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; + }, + + submit: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "submit" === elem.type; + }, + + image: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; + }, + + reset: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "reset" === elem.type; + }, + + button: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && "button" === elem.type || name === "button"; + }, + + input: function( elem ) { + return (/input|select|textarea|button/i).test( elem.nodeName ); + }, + + focus: function( elem ) { + return elem === elem.ownerDocument.activeElement; + } + }, + setFilters: { + first: function( elem, i ) { + return i === 0; + }, + + last: function( elem, i, match, array ) { + return i === array.length - 1; + }, + + even: function( elem, i ) { + return i % 2 === 0; + }, + + odd: function( elem, i ) { + return i % 2 === 1; + }, + + lt: function( elem, i, match ) { + return i < match[3] - 0; + }, + + gt: function( elem, i, match ) { + return i > match[3] - 0; + }, + + nth: function( elem, i, match ) { + return match[3] - 0 === i; + }, + + eq: function( elem, i, match ) { + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function( elem, match, i, array ) { + var name = match[1], + filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; + + } else if ( name === "not" ) { + var not = match[3]; + + for ( var j = 0, l = not.length; j < l; j++ ) { + if ( not[j] === elem ) { + return false; + } + } + + return true; + + } else { + Sizzle.error( name ); + } + }, + + CHILD: function( elem, match ) { + var type = match[1], + node = elem; + + switch ( type ) { + case "only": + case "first": + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + if ( type === "first" ) { + return true; + } + + node = elem; + + case "last": + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + return true; + + case "nth": + var first = match[2], + last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + + if ( first === 0 ) { + return diff === 0; + + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + + ID: function( elem, match ) { + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + + TAG: function( elem, match ) { + return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; + }, + + CLASS: function( elem, match ) { + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + + ATTR: function( elem, match ) { + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + + POS: function( elem, match, i, array ) { + var name = match[2], + filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS, + fescape = function(all, num){ + return "\\" + (num - 0 + 1); + }; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); +} + +var makeArray = function( array, results ) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; + +// Provide a fallback method if it does not work +} catch( e ) { + makeArray = function( array, results ) { + var i = 0, + ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + + } else { + if ( typeof array.length === "number" ) { + for ( var l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + + } else { + for ( ; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder, siblingCheck; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + return a.compareDocumentPosition ? -1 : 1; + } + + return a.compareDocumentPosition(b) & 4 ? -1 : 1; + }; + +} else { + sortOrder = function( a, b ) { + // The nodes are identical, we can exit early + if ( a === b ) { + hasDuplicate = true; + return 0; + + // Fallback to using sourceIndex (in IE) if it's available on both nodes + } else if ( a.sourceIndex && b.sourceIndex ) { + return a.sourceIndex - b.sourceIndex; + } + + var al, bl, + ap = [], + bp = [], + aup = a.parentNode, + bup = b.parentNode, + cur = aup; + + // If the nodes are siblings (or identical) we can do a quick check + if ( aup === bup ) { + return siblingCheck( a, b ); + + // If no parents were found then the nodes are disconnected + } else if ( !aup ) { + return -1; + + } else if ( !bup ) { + return 1; + } + + // Otherwise they're somewhere else in the tree so we need + // to build up a full list of the parentNodes for comparison + while ( cur ) { + ap.unshift( cur ); + cur = cur.parentNode; + } + + cur = bup; + + while ( cur ) { + bp.unshift( cur ); + cur = cur.parentNode; + } + + al = ap.length; + bl = bp.length; + + // Start walking down the tree looking for a discrepancy + for ( var i = 0; i < al && i < bl; i++ ) { + if ( ap[i] !== bp[i] ) { + return siblingCheck( ap[i], bp[i] ); + } + } + + // We ended someplace up the tree so do a sibling check + return i === al ? + siblingCheck( a, bp[i], -1 ) : + siblingCheck( ap[i], b, 1 ); + }; + + siblingCheck = function( a, b, ret ) { + if ( a === b ) { + return ret; + } + + var cur = a.nextSibling; + + while ( cur ) { + if ( cur === b ) { + return -1; + } + + cur = cur.nextSibling; + } + + return 1; + }; +} + +// Utility function for retreiving the text value of an array of DOM nodes +Sizzle.getText = function( elems ) { + var ret = "", elem; + + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; + + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += Sizzle.getText( elem.childNodes ); + } + } + + return ret; +}; + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date()).getTime(), + root = document.documentElement; + + form.innerHTML = ""; + + // Inject it into the root element, check its status, and remove it quickly + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( document.getElementById( id ) ) { + Expr.find.ID = function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + + return m ? + m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? + [m] : + undefined : + []; + } + }; + + Expr.filter.ID = function( elem, match ) { + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + + // release memory in IE + root = form = null; +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function( match, context ) { + var results = context.getElementsByTagName( match[1] ); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = ""; + + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + + Expr.attrHandle.href = function( elem ) { + return elem.getAttribute( "href", 2 ); + }; + } + + // release memory in IE + div = null; +})(); + +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, + div = document.createElement("div"), + id = "__sizzle__"; + + div.innerHTML = "

    "; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function( query, context, extra, seed ) { + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && !Sizzle.isXML(context) ) { + // See if we find a selector to speed up + var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); + + if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { + // Speed-up: Sizzle("TAG") + if ( match[1] ) { + return makeArray( context.getElementsByTagName( query ), extra ); + + // Speed-up: Sizzle(".CLASS") + } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { + return makeArray( context.getElementsByClassName( match[2] ), extra ); + } + } + + if ( context.nodeType === 9 ) { + // Speed-up: Sizzle("body") + // The body element only exists once, optimize finding it + if ( query === "body" && context.body ) { + return makeArray( [ context.body ], extra ); + + // Speed-up: Sizzle("#ID") + } else if ( match && match[3] ) { + var elem = context.getElementById( match[3] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id === match[3] ) { + return makeArray( [ elem ], extra ); + } + + } else { + return makeArray( [], extra ); + } + } + + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(qsaError) {} + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + var oldContext = context, + old = context.getAttribute( "id" ), + nid = old || id, + hasParent = context.parentNode, + relativeHierarchySelector = /^\s*[+~]/.test( query ); + + if ( !old ) { + context.setAttribute( "id", nid ); + } else { + nid = nid.replace( /'/g, "\\$&" ); + } + if ( relativeHierarchySelector && hasParent ) { + context = context.parentNode; + } + + try { + if ( !relativeHierarchySelector || hasParent ) { + return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); + } + + } catch(pseudoError) { + } finally { + if ( !old ) { + oldContext.removeAttribute( "id" ); + } + } + } + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + // release memory in IE + div = null; + })(); +} + +(function(){ + var html = document.documentElement, + matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; + + if ( matches ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9 fails this) + var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), + pseudoWorks = false; + + try { + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( document.documentElement, "[test!='']:sizzle" ); + + } catch( pseudoError ) { + pseudoWorks = true; + } + + Sizzle.matchesSelector = function( node, expr ) { + // Make sure that attribute selectors are quoted + expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); + + if ( !Sizzle.isXML( node ) ) { + try { + if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { + var ret = matches.call( node, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || !disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9, so check for that + node.document && node.document.nodeType !== 11 ) { + return ret; + } + } + } catch(e) {} + } + + return Sizzle(expr, null, null, [node]).length > 0; + }; + } +})(); + +(function(){ + var div = document.createElement("div"); + + div.innerHTML = "
    "; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function( match, context, isXML ) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + // release memory in IE + div = null; +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +if ( document.documentElement.contains ) { + Sizzle.contains = function( a, b ) { + return a !== b && (a.contains ? a.contains(b) : true); + }; + +} else if ( document.documentElement.compareDocumentPosition ) { + Sizzle.contains = function( a, b ) { + return !!(a.compareDocumentPosition(b) & 16); + }; + +} else { + Sizzle.contains = function() { + return false; + }; +} + +Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +var posProcess = function( selector, context ) { + var match, + tmpSet = [], + later = "", + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.filters; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})(); + + +var runtil = /Until$/, + rparentsprev = /^(?:parents|prevUntil|prevAll)/, + // Note: This RegExp should be improved, or likely pulled from Sizzle + rmultiselector = /,/, + isSimple = /^.[^:#\[\.,]*$/, + slice = Array.prototype.slice, + POS = jQuery.expr.match.POS, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend({ + find: function( selector ) { + var self = this, + i, l; + + if ( typeof selector !== "string" ) { + return jQuery( selector ).filter(function() { + for ( i = 0, l = self.length; i < l; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }); + } + + var ret = this.pushStack( "", "find", selector ), + length, n, r; + + for ( i = 0, l = this.length; i < l; i++ ) { + length = ret.length; + jQuery.find( selector, this[i], ret ); + + if ( i > 0 ) { + // Make sure that the results are unique + for ( n = length; n < ret.length; n++ ) { + for ( r = 0; r < length; r++ ) { + if ( ret[r] === ret[n] ) { + ret.splice(n--, 1); + break; + } + } + } + } + } + + return ret; + }, + + has: function( target ) { + var targets = jQuery( target ); + return this.filter(function() { + for ( var i = 0, l = targets.length; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false), "not", selector); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true), "filter", selector ); + }, + + is: function( selector ) { + return !!selector && ( typeof selector === "string" ? + jQuery.filter( selector, this ).length > 0 : + this.filter( selector ).length > 0 ); + }, + + closest: function( selectors, context ) { + var ret = [], i, l, cur = this[0]; + + // Array + if ( jQuery.isArray( selectors ) ) { + var match, selector, + matches = {}, + level = 1; + + if ( cur && selectors.length ) { + for ( i = 0, l = selectors.length; i < l; i++ ) { + selector = selectors[i]; + + if ( !matches[ selector ] ) { + matches[ selector ] = POS.test( selector ) ? + jQuery( selector, context || this.context ) : + selector; + } + } + + while ( cur && cur.ownerDocument && cur !== context ) { + for ( selector in matches ) { + match = matches[ selector ]; + + if ( match.jquery ? match.index( cur ) > -1 : jQuery( cur ).is( match ) ) { + ret.push({ selector: selector, elem: cur, level: level }); + } + } + + cur = cur.parentNode; + level++; + } + } + + return ret; + } + + // String + var pos = POS.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( i = 0, l = this.length; i < l; i++ ) { + cur = this[i]; + + while ( cur ) { + if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { + ret.push( cur ); + break; + + } else { + cur = cur.parentNode; + if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) { + break; + } + } + } + } + + ret = ret.length > 1 ? jQuery.unique( ret ) : ret; + + return this.pushStack( ret, "closest", selectors ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[0], jQuery( elem ) ); + } + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context ) : + jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? + all : + jQuery.unique( all ) ); + }, + + andSelf: function() { + return this.add( this.prevObject ); + } +}); + +// A painfully simple check to see if an element is disconnected +// from a document (should be improved, where feasible). +function isDisconnected( node ) { + return !node || !node.parentNode || node.parentNode.nodeType === 11; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return jQuery.nth( elem, 2, "nextSibling" ); + }, + prev: function( elem ) { + return jQuery.nth( elem, 2, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( elem.parentNode.firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.makeArray( elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ), + // The variable 'args' was introduced in + // https://github.com/jquery/jquery/commit/52a0238 + // to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed. + // http://code.google.com/p/v8/issues/detail?id=1050 + args = slice.call(arguments); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; + + if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret, name, args.join(",") ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 ? + jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : + jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + nth: function( cur, result, dir, elem ) { + result = result || 1; + var num = 0; + + for ( ; cur; cur = cur[dir] ) { + if ( cur.nodeType === 1 && ++num === result ) { + break; + } + } + + return cur; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, keep ) { + + // Can't pass null or undefined to indexOf in Firefox 4 + // Set to 0 to skip string check + qualifier = qualifier || 0; + + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + var retVal = !!qualifier.call( elem, i, elem ); + return retVal === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem, i ) { + return (elem === qualifier) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem, i ) { + return (jQuery.inArray( elem, qualifier ) >= 0) === keep; + }); +} + + + + +var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, + rtagName = /<([\w:]+)/, + rtbody = /", "" ], + legend: [ 1, "
    ", "
    " ], + thead: [ 1, "", "
    " ], + tr: [ 2, "", "
    " ], + td: [ 3, "", "
    " ], + col: [ 2, "", "
    " ], + area: [ 1, "", "" ], + _default: [ 0, "", "" ] + }; + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// IE can't serialize and \n"); + return sb.toString(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("ScriptConfigItem, executing "); + sb.append(script); + sb.append(' '); + sb.append(args); + return sb.toString(); + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/VRScripts.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/VRScripts.java new file mode 100644 index 0000000000..39d8bec62f --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/VRScripts.java @@ -0,0 +1,69 @@ +// +// 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.agent.resource.virtualnetwork; + +public class VRScripts { + public final static String CONFIG_PERSIST_LOCATION = "/var/cache/cloud/"; + public final static String IP_ASSOCIATION_CONFIG = "ip_associations.json"; + public final static String GUEST_NETWORK_CONFIG = "guest_network.json"; + public final static String NETWORK_ACL_CONFIG = "network_acl.json"; + public final static String VM_METADATA_CONFIG = "vm_metadata.json"; + public final static String VM_DHCP_CONFIG = "vm_dhcp_entry.json"; + public final static String VM_PASSWORD_CONFIG = "vm_password.json"; + public static final String FORWARDING_RULES_CONFIG = "forwarding_rules.json"; + public static final String FIREWALL_RULES_CONFIG = "firewall_rules.json"; + public static final String VPN_USER_LIST_CONFIG = "vpn_user_list.json"; + public static final String STATICNAT_RULES_CONFIG = "staticnat_rules.json"; + public static final String SITE_2_SITE_VPN_CONFIG = "site_2_site_vpn.json"; + public static final String STATIC_ROUTES_CONFIG = "static_routes.json"; + public static final String REMOTE_ACCESS_VPN_CONFIG = "remote_access_vpn.json"; + public static final String MONITOR_SERVICE_CONFIG = "monitor_service.json"; + public static final String DHCP_CONFIG = "dhcp.json"; + public static final String IP_ALIAS_CONFIG = "ip_aliases.json"; + public static final String LOAD_BALANCER_CONFIG = "load_balancer.json"; + + public final static String CONFIG_CACHE_LOCATION = "/var/cache/cloud/"; + public final static int DEFAULT_EXECUTEINVR_TIMEOUT = 120; //Seconds + + // New scripts for use with chef + public static final String UPDATE_CONFIG = "update_config.py"; + + // TODO Remove scripts + public static final String S2SVPN_CHECK = "checkbatchs2svpn.sh"; + public static final String S2SVPN_IPSEC = "ipsectunnel.sh"; + public static final String DHCP = "edithosts.sh"; + public static final String DNSMASQ_CONFIG = "dnsmasq.sh"; + public static final String IPASSOC = "ipassoc.sh"; + public static final String LB = "loadbalancer.sh"; + public static final String MONITOR_SERVICE = "monitor_service.sh"; + public static final String PASSWORD = "savepassword.sh"; + public static final String ROUTER_ALERTS = "getRouterAlerts.sh"; + public static final String RVR_CHECK = "checkrouter.sh"; + public static final String VMDATA = "vmdata.py"; + public static final String RVR_BUMPUP_PRI = "bumpup_priority.sh"; + public static final String VERSION = "get_template_version.sh"; + public static final String VPC_SOURCE_NAT = "vpc_snat.sh"; + public static final String VPC_STATIC_ROUTE = "vpc_staticroute.sh"; + public static final String VPN_L2TP = "vpn_l2tp.sh"; + public static final String UPDATE_HOST_PASSWD = "update_host_passwd.sh"; + + public static final String VR_CFG = "vr_cfg.sh"; + +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/VirtualRouterDeployer.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/VirtualRouterDeployer.java new file mode 100644 index 0000000000..f6a1694c55 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/VirtualRouterDeployer.java @@ -0,0 +1,32 @@ +// +// 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.agent.resource.virtualnetwork; + +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.utils.ExecutionResult; + +public interface VirtualRouterDeployer { + ExecutionResult executeInVR(String routerIp, String script, String args); + /* timeout in seconds */ + ExecutionResult executeInVR(String routerIp, String script, String args, int timeout); + ExecutionResult createFileInVR(String routerIp, String path, String filename, String content); + ExecutionResult prepareCommand(NetworkElementCommand cmd); + ExecutionResult cleanupCommand(NetworkElementCommand cmd); +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java new file mode 100644 index 0000000000..0b161b4021 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java @@ -0,0 +1,398 @@ +// +// 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.agent.resource.virtualnetwork; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.UUID; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import javax.naming.ConfigurationException; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.CheckRouterAnswer; +import com.cloud.agent.api.CheckRouterCommand; +import com.cloud.agent.api.CheckS2SVpnConnectionsAnswer; +import com.cloud.agent.api.CheckS2SVpnConnectionsCommand; +import com.cloud.agent.api.GetDomRVersionAnswer; +import com.cloud.agent.api.GetDomRVersionCmd; +import com.cloud.agent.api.GetRouterAlertsAnswer; +import com.cloud.agent.api.routing.AggregationControlCommand; +import com.cloud.agent.api.routing.AggregationControlCommand.Action; +import com.cloud.agent.api.routing.GetRouterAlertsCommand; +import com.cloud.agent.api.routing.GroupAnswer; +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.resource.virtualnetwork.facade.AbstractConfigItemFacade; +import com.cloud.utils.ExecutionResult; +import com.cloud.utils.NumbersUtil; +import com.cloud.utils.exception.CloudRuntimeException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * VirtualNetworkResource controls and configures virtual networking + * + * @config + * {@table + * || Param Name | Description | Values | Default || + * } + **/ +public class VirtualRoutingResource { + + private static final Logger s_logger = LoggerFactory.getLogger(VirtualRoutingResource.class); + private VirtualRouterDeployer _vrDeployer; + private Map> _vrAggregateCommandsSet; + protected Map _vrLockMap = new HashMap(); + + private String _name; + private int _sleep; + private int _retry; + private int _port; + private int _eachTimeout; + + private String _cfgVersion = "1.0"; + + public VirtualRoutingResource(VirtualRouterDeployer deployer) { + _vrDeployer = deployer; + } + + public Answer executeRequest(final NetworkElementCommand cmd) { + boolean aggregated = false; + String routerName = cmd.getAccessDetail(NetworkElementCommand.ROUTER_NAME); + Lock lock; + if (_vrLockMap.containsKey(routerName)) { + lock = _vrLockMap.get(routerName); + } else { + lock = new ReentrantLock(); + _vrLockMap.put(routerName, lock); + } + lock.lock(); + + try { + ExecutionResult rc = _vrDeployer.prepareCommand(cmd); + if (!rc.isSuccess()) { + s_logger.error("Failed to prepare VR command due to " + rc.getDetails()); + return new Answer(cmd, false, rc.getDetails()); + } + + assert cmd.getRouterAccessIp() != null : "Why there is no access IP for VR?"; + + if (cmd.isQuery()) { + return executeQueryCommand(cmd); + } + + if (cmd instanceof AggregationControlCommand) { + return execute((AggregationControlCommand)cmd); + } + + if (_vrAggregateCommandsSet.containsKey(routerName)) { + _vrAggregateCommandsSet.get(routerName).add(cmd); + aggregated = true; + // Clean up would be done after command has been executed + //TODO: Deal with group answer as well + return new Answer(cmd); + } + + List cfg = generateCommandCfg(cmd); + if (cfg == null) { + return Answer.createUnsupportedCommandAnswer(cmd); + } + + return applyConfig(cmd, cfg); + } catch (final IllegalArgumentException e) { + return new Answer(cmd, false, e.getMessage()); + } finally { + lock.unlock(); + if (!aggregated) { + ExecutionResult rc = _vrDeployer.cleanupCommand(cmd); + if (!rc.isSuccess()) { + s_logger.error("Failed to cleanup VR command due to " + rc.getDetails()); + } + } + } + } + + private Answer executeQueryCommand(NetworkElementCommand cmd) { + if (cmd instanceof CheckRouterCommand) { + return execute((CheckRouterCommand)cmd); + } else if (cmd instanceof GetDomRVersionCmd) { + return execute((GetDomRVersionCmd)cmd); + } else if (cmd instanceof CheckS2SVpnConnectionsCommand) { + return execute((CheckS2SVpnConnectionsCommand) cmd); + } else if (cmd instanceof GetRouterAlertsCommand) { + return execute((GetRouterAlertsCommand)cmd); + } else { + s_logger.error("Unknown query command in VirtualRoutingResource!"); + return Answer.createUnsupportedCommandAnswer(cmd); + } + } + + private ExecutionResult applyConfigToVR(String routerAccessIp, ConfigItem c) { + return applyConfigToVR(routerAccessIp, c, VRScripts.DEFAULT_EXECUTEINVR_TIMEOUT); + } + + private ExecutionResult applyConfigToVR(String routerAccessIp, ConfigItem c, int timeout) { + if (c instanceof FileConfigItem) { + FileConfigItem configItem = (FileConfigItem)c; + + return _vrDeployer.createFileInVR(routerAccessIp, configItem.getFilePath(), configItem.getFileName(), configItem.getFileContents()); + } else if (c instanceof ScriptConfigItem) { + ScriptConfigItem configItem = (ScriptConfigItem)c; + return _vrDeployer.executeInVR(routerAccessIp, configItem.getScript(), configItem.getArgs(), timeout); + } + throw new CloudRuntimeException("Unable to apply unknown configitem of type " + c.getClass().getSimpleName()); + } + + + private Answer applyConfig(NetworkElementCommand cmd, List cfg) { + + + if (cfg.isEmpty()) { + return new Answer(cmd, true, "Nothing to do"); + } + + List results = new ArrayList(); + List details = new ArrayList(); + boolean finalResult = false; + for (ConfigItem configItem : cfg) { + long startTimestamp = System.currentTimeMillis(); + ExecutionResult result = applyConfigToVR(cmd.getRouterAccessIp(), configItem); + if (s_logger.isDebugEnabled()) { + long elapsed = System.currentTimeMillis() - startTimestamp; + s_logger.debug("Processing " + configItem + " took " + elapsed + "ms"); + } + if (result == null) { + result = new ExecutionResult(false, "null execution result"); + } + results.add(result); + details.add(configItem.getInfo() + (result.isSuccess() ? " - success: " : " - failed: ") + result.getDetails()); + finalResult = result.isSuccess(); + } + + // Not sure why this matters, but log it anyway + if (cmd.getAnswersCount() != results.size()) { + s_logger.warn("Expected " + cmd.getAnswersCount() + " answers while executing " + cmd.getClass().getSimpleName() + " but received " + results.size()); + } + + + if (results.size() == 1) { + return new Answer(cmd, finalResult, results.get(0).getDetails()); + } else { + return new GroupAnswer(cmd, finalResult, results.size(), details.toArray(new String[details.size()])); + } + } + + private CheckS2SVpnConnectionsAnswer execute(CheckS2SVpnConnectionsCommand cmd) { + + StringBuffer buff = new StringBuffer(); + for (String ip : cmd.getVpnIps()) { + buff.append(ip); + buff.append(" "); + } + ExecutionResult result = _vrDeployer.executeInVR(cmd.getRouterAccessIp(), VRScripts.S2SVPN_CHECK, buff.toString()); + return new CheckS2SVpnConnectionsAnswer(cmd, result.isSuccess(), result.getDetails()); + } + + private GetRouterAlertsAnswer execute(GetRouterAlertsCommand cmd) { + + String routerIp = cmd.getAccessDetail(NetworkElementCommand.ROUTER_IP); + String args = cmd.getPreviousAlertTimeStamp(); + + ExecutionResult result = _vrDeployer.executeInVR(routerIp, VRScripts.ROUTER_ALERTS, args); + String alerts[] = null; + String lastAlertTimestamp = null; + + if (result.isSuccess()) { + if (!result.getDetails().isEmpty() && !result.getDetails().trim().equals("No Alerts")) { + alerts = result.getDetails().trim().split("\\\\n"); + String[] lastAlert = alerts[alerts.length - 1].split(","); + lastAlertTimestamp = lastAlert[0]; + } + return new GetRouterAlertsAnswer(cmd, alerts, lastAlertTimestamp); + } else { + return new GetRouterAlertsAnswer(cmd, result.getDetails()); + } + } + + private Answer execute(CheckRouterCommand cmd) { + final ExecutionResult result = _vrDeployer.executeInVR(cmd.getRouterAccessIp(), VRScripts.RVR_CHECK, null); + if (!result.isSuccess()) { + return new CheckRouterAnswer(cmd, result.getDetails()); + } + return new CheckRouterAnswer(cmd, result.getDetails(), true); + } + + private Answer execute(GetDomRVersionCmd cmd) { + final ExecutionResult result = _vrDeployer.executeInVR(cmd.getRouterAccessIp(), VRScripts.VERSION, null); + if (!result.isSuccess()) { + return new GetDomRVersionAnswer(cmd, "GetDomRVersionCmd failed"); + } + String[] lines = result.getDetails().split("&"); + if (lines.length != 2) { + return new GetDomRVersionAnswer(cmd, result.getDetails()); + } + return new GetDomRVersionAnswer(cmd, result.getDetails(), lines[0], lines[1]); + } + + public boolean configure(final String name, final Map params) throws ConfigurationException { + _name = name; + + String value = (String)params.get("ssh.sleep"); + _sleep = NumbersUtil.parseInt(value, 10) * 1000; + + value = (String)params.get("ssh.retry"); + _retry = NumbersUtil.parseInt(value, 36); + + value = (String)params.get("ssh.port"); + _port = NumbersUtil.parseInt(value, 3922); + + value = (String)params.get("router.aggregation.command.each.timeout"); + _eachTimeout = NumbersUtil.parseInt(value, 3); + + if (_vrDeployer == null) { + throw new ConfigurationException("Unable to find the resource for VirtualRouterDeployer!"); + } + + _vrAggregateCommandsSet = new HashMap<>(); + return true; + } + + public boolean connect(final String ipAddress) { + return connect(ipAddress, _port); + } + + public boolean connect(final String ipAddress, final int port) { + return connect(ipAddress, port, _sleep); + } + + public boolean connect(final String ipAddress, int retry, int sleep) { + for (int i = 0; i <= retry; i++) { + SocketChannel sch = null; + try { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Trying to connect to " + ipAddress); + } + sch = SocketChannel.open(); + sch.configureBlocking(true); + + final InetSocketAddress addr = new InetSocketAddress(ipAddress, _port); + sch.connect(addr); + return true; + } catch (final IOException e) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Could not connect to " + ipAddress); + } + } finally { + if (sch != null) { + try { + sch.close(); + } catch (final IOException e) { + } + } + } + try { + Thread.sleep(sleep); + } catch (final InterruptedException e) { + } + } + + s_logger.debug("Unable to logon to " + ipAddress); + + return false; + } + + private List generateCommandCfg(NetworkElementCommand cmd) { + s_logger.debug("Transforming " + cmd.getClass().getCanonicalName() + " to ConfigItems"); + + final AbstractConfigItemFacade configItemFacade = AbstractConfigItemFacade.getInstance(cmd.getClass()); + + return configItemFacade.generateConfig(cmd); + } + + private Answer execute(AggregationControlCommand cmd) { + Action action = cmd.getAction(); + String routerName = cmd.getAccessDetail(NetworkElementCommand.ROUTER_NAME); + assert routerName != null; + assert cmd.getRouterAccessIp() != null; + + if (action == Action.Start) { + assert (!_vrAggregateCommandsSet.containsKey(routerName)); + + Queue queue = new LinkedBlockingQueue<>(); + _vrAggregateCommandsSet.put(routerName, queue); + return new Answer(cmd, true, "Command aggregation started"); + } else if (action == Action.Finish) { + Queue queue = _vrAggregateCommandsSet.get(routerName); + int answerCounts = 0; + try { + StringBuilder sb = new StringBuilder(); + sb.append("#Apache CloudStack Virtual Router Config File\n"); + sb.append("\n" + _cfgVersion + "\n\n"); + for (NetworkElementCommand command : queue) { + answerCounts += command.getAnswersCount(); + List cfg = generateCommandCfg(command); + if (cfg == null) { + s_logger.warn("Unknown commands for VirtualRoutingResource, but continue: " + cmd.toString()); + continue; + } + + for (ConfigItem c : cfg) { + sb.append(c.getAggregateCommand()); + } + } + + // TODO replace with applyConfig with a stop on fail + String cfgFileName = "VR-"+ UUID.randomUUID().toString() + ".cfg"; + FileConfigItem fileConfigItem = new FileConfigItem(VRScripts.CONFIG_CACHE_LOCATION, cfgFileName, sb.toString()); + ScriptConfigItem scriptConfigItem = new ScriptConfigItem(VRScripts.VR_CFG, "-c " + VRScripts.CONFIG_CACHE_LOCATION + cfgFileName); + // 120s is the minimal timeout + int timeout = answerCounts * _eachTimeout; + if (timeout < 120) { + timeout = 120; + } + + ExecutionResult result = applyConfigToVR(cmd.getRouterAccessIp(), fileConfigItem); + if (!result.isSuccess()) { + return new Answer(cmd, false, result.getDetails()); + } + + result = applyConfigToVR(cmd.getRouterAccessIp(), scriptConfigItem, timeout); + if (!result.isSuccess()) { + return new Answer(cmd, false, result.getDetails()); + } + + return new Answer(cmd, true, "Command aggregation finished"); + } finally { + queue.clear(); + _vrAggregateCommandsSet.remove(routerName); + } + } + return new Answer(cmd, false, "Fail to recongize aggregation action " + action.toString()); + } +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/AbstractConfigItemFacade.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/AbstractConfigItemFacade.java new file mode 100644 index 0000000000..eb4138f523 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/AbstractConfigItemFacade.java @@ -0,0 +1,138 @@ +// +// 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.agent.resource.virtualnetwork.facade; + +import java.util.Hashtable; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; + +import com.cloud.agent.api.BumpUpPriorityCommand; +import com.cloud.agent.api.SetupGuestNetworkCommand; +import com.cloud.agent.api.routing.CreateIpAliasCommand; +import com.cloud.agent.api.routing.DeleteIpAliasCommand; +import com.cloud.agent.api.routing.DhcpEntryCommand; +import com.cloud.agent.api.routing.DnsMasqConfigCommand; +import com.cloud.agent.api.routing.IpAssocCommand; +import com.cloud.agent.api.routing.IpAssocVpcCommand; +import com.cloud.agent.api.routing.LoadBalancerConfigCommand; +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.api.routing.RemoteAccessVpnCfgCommand; +import com.cloud.agent.api.routing.SavePasswordCommand; +import com.cloud.agent.api.routing.SetFirewallRulesCommand; +import com.cloud.agent.api.routing.SetMonitorServiceCommand; +import com.cloud.agent.api.routing.SetNetworkACLCommand; +import com.cloud.agent.api.routing.SetPortForwardingRulesCommand; +import com.cloud.agent.api.routing.SetPortForwardingRulesVpcCommand; +import com.cloud.agent.api.routing.SetSourceNatCommand; +import com.cloud.agent.api.routing.SetStaticNatRulesCommand; +import com.cloud.agent.api.routing.SetStaticRouteCommand; +import com.cloud.agent.api.routing.Site2SiteVpnCfgCommand; +import com.cloud.agent.api.routing.VmDataCommand; +import com.cloud.agent.api.routing.VpnUsersCfgCommand; +import com.cloud.agent.resource.virtualnetwork.ConfigItem; +import com.cloud.agent.resource.virtualnetwork.FileConfigItem; +import com.cloud.agent.resource.virtualnetwork.ScriptConfigItem; +import com.cloud.agent.resource.virtualnetwork.VRScripts; +import com.cloud.agent.resource.virtualnetwork.model.ConfigBase; +import com.cloud.utils.exception.CloudRuntimeException; +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class AbstractConfigItemFacade { + + private static final Logger s_logger = LoggerFactory.getLogger(AbstractConfigItemFacade.class); + + private final static Gson gson; + + private static Hashtable, AbstractConfigItemFacade> flyweight = new Hashtable, AbstractConfigItemFacade>(); + + static { + gson = new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .disableHtmlEscaping() + .create(); + + flyweight.put(SetPortForwardingRulesVpcCommand.class, new SetPortForwardingRulesVpcConfigItem()); + flyweight.put(SetPortForwardingRulesCommand.class, new SetPortForwardingRulesConfigItem()); + flyweight.put(SetStaticRouteCommand.class, new SetStaticRouteConfigItem()); + flyweight.put(SetStaticNatRulesCommand.class, new SetStaticNatRulesConfigItem()); + flyweight.put(LoadBalancerConfigCommand.class, new LoadBalancerConfigItem()); + flyweight.put(SavePasswordCommand.class, new SavePasswordConfigItem()); + flyweight.put(DhcpEntryCommand.class, new DhcpEntryConfigItem()); + flyweight.put(CreateIpAliasCommand.class, new CreateIpAliasConfigItem()); + flyweight.put(DnsMasqConfigCommand.class, new DnsMasqConfigItem()); + flyweight.put(DeleteIpAliasCommand.class, new DeleteIpAliasConfigItem()); + flyweight.put(VmDataCommand.class, new VmDataConfigItem()); + flyweight.put(SetFirewallRulesCommand.class, new SetFirewallRulesConfigItem()); + flyweight.put(BumpUpPriorityCommand.class, new BumpUpPriorityConfigItem()); + flyweight.put(RemoteAccessVpnCfgCommand.class, new RemoteAccessVpnConfigItem()); + flyweight.put(VpnUsersCfgCommand.class, new VpnUsersConfigItem()); + flyweight.put(Site2SiteVpnCfgCommand.class, new Site2SiteVpnConfigItem()); + flyweight.put(SetMonitorServiceCommand.class, new SetMonitorServiceConfigItem()); + flyweight.put(SetupGuestNetworkCommand.class, new SetGuestNetworkConfigItem()); + flyweight.put(SetNetworkACLCommand.class, new SetNetworkAclConfigItem()); + flyweight.put(SetSourceNatCommand.class, new SetSourceNatConfigItem()); + flyweight.put(IpAssocCommand.class, new IpAssociationConfigItem()); + flyweight.put(IpAssocVpcCommand.class, new IpAssociationConfigItem()); + } + + protected String destinationFile; + + public static AbstractConfigItemFacade getInstance(final Class key) { + if (!flyweight.containsKey(key)) { + throw new CloudRuntimeException("Unable to process the configuration for " + key.getClass().getName()); + } + + final AbstractConfigItemFacade instance = flyweight.get(key); + + return instance; + } + + + private static String appendUuidToJsonFiles(final String filename) { + String remoteFileName = new String(filename); + if (remoteFileName.endsWith("json")) { + remoteFileName += "." + UUID.randomUUID().toString(); + } + return remoteFileName; + } + + protected List generateConfigItems(final ConfigBase configuration) { + final List cfg = new LinkedList<>(); + + final String remoteFilename = appendUuidToJsonFiles(destinationFile); + s_logger.debug("Transformed filename " + destinationFile + " to " + remoteFilename); + + final ConfigItem configFile = new FileConfigItem(VRScripts.CONFIG_PERSIST_LOCATION, remoteFilename, gson.toJson(configuration)); + cfg.add(configFile); + + final ConfigItem updateCommand = new ScriptConfigItem(VRScripts.UPDATE_CONFIG, remoteFilename); + cfg.add(updateCommand); + + return cfg; + } + + public abstract List generateConfig(NetworkElementCommand cmd); +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/BumpUpPriorityConfigItem.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/BumpUpPriorityConfigItem.java new file mode 100644 index 0000000000..425057d58f --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/BumpUpPriorityConfigItem.java @@ -0,0 +1,39 @@ +// +// 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.agent.resource.virtualnetwork.facade; + +import java.util.LinkedList; +import java.util.List; + +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.resource.virtualnetwork.ConfigItem; +import com.cloud.agent.resource.virtualnetwork.ScriptConfigItem; +import com.cloud.agent.resource.virtualnetwork.VRScripts; + +public class BumpUpPriorityConfigItem extends AbstractConfigItemFacade { + + @Override + public List generateConfig(final NetworkElementCommand cmd) { + final LinkedList cfg = new LinkedList<>(); + cfg.add(new ScriptConfigItem(VRScripts.RVR_BUMPUP_PRI, null)); + + return cfg; + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/CreateIpAliasConfigItem.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/CreateIpAliasConfigItem.java new file mode 100644 index 0000000000..4eca3ba832 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/CreateIpAliasConfigItem.java @@ -0,0 +1,57 @@ +// +// 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.agent.resource.virtualnetwork.facade; + +import java.util.LinkedList; +import java.util.List; + +import com.cloud.agent.api.routing.CreateIpAliasCommand; +import com.cloud.agent.api.routing.IpAliasTO; +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.resource.virtualnetwork.ConfigItem; +import com.cloud.agent.resource.virtualnetwork.VRScripts; +import com.cloud.agent.resource.virtualnetwork.model.ConfigBase; +import com.cloud.agent.resource.virtualnetwork.model.IpAddressAlias; +import com.cloud.agent.resource.virtualnetwork.model.IpAliases; + +public class CreateIpAliasConfigItem extends AbstractConfigItemFacade { + + @Override + public List generateConfig(final NetworkElementCommand cmd) { + final CreateIpAliasCommand command = (CreateIpAliasCommand) cmd; + + final List ipAliases = new LinkedList(); + final List ipAliasTOs = command.getIpAliasList(); + for (final IpAliasTO ipaliasto : ipAliasTOs) { + final IpAddressAlias alias = new IpAddressAlias(false, ipaliasto.getRouterip(), ipaliasto.getNetmask(), Long.parseLong(ipaliasto.getAlias_count())); + ipAliases.add(alias); + } + + final IpAliases ipAliasList = new IpAliases(ipAliases); + return generateConfigItems(ipAliasList); + } + + @Override + protected List generateConfigItems(final ConfigBase configuration) { + destinationFile = VRScripts.IP_ALIAS_CONFIG; + + return super.generateConfigItems(configuration); + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/DeleteIpAliasConfigItem.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/DeleteIpAliasConfigItem.java new file mode 100644 index 0000000000..82fc870fce --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/DeleteIpAliasConfigItem.java @@ -0,0 +1,64 @@ +// +// 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.agent.resource.virtualnetwork.facade; + +import java.util.LinkedList; +import java.util.List; + +import com.cloud.agent.api.routing.DeleteIpAliasCommand; +import com.cloud.agent.api.routing.IpAliasTO; +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.resource.virtualnetwork.ConfigItem; +import com.cloud.agent.resource.virtualnetwork.VRScripts; +import com.cloud.agent.resource.virtualnetwork.model.ConfigBase; +import com.cloud.agent.resource.virtualnetwork.model.IpAddressAlias; +import com.cloud.agent.resource.virtualnetwork.model.IpAliases; + +public class DeleteIpAliasConfigItem extends AbstractConfigItemFacade { + + @Override + public List generateConfig(final NetworkElementCommand cmd) { + final DeleteIpAliasCommand command = (DeleteIpAliasCommand) cmd; + + final List ipAliases = new LinkedList(); + + final List revokedIpAliasTOs = command.getDeleteIpAliasTos(); + for (final IpAliasTO ipAliasTO : revokedIpAliasTOs) { + final IpAddressAlias alias = new IpAddressAlias(true, ipAliasTO.getRouterip(), ipAliasTO.getNetmask(), Long.parseLong(ipAliasTO.getAlias_count())); + ipAliases.add(alias); + } + + final List activeIpAliasTOs = command.getCreateIpAliasTos(); + for (final IpAliasTO ipAliasTO : activeIpAliasTOs) { + final IpAddressAlias alias = new IpAddressAlias(false, ipAliasTO.getRouterip(), ipAliasTO.getNetmask(), Long.parseLong(ipAliasTO.getAlias_count())); + ipAliases.add(alias); + } + + final IpAliases ipAliasList = new IpAliases(ipAliases); + return generateConfigItems(ipAliasList); + } + + @Override + protected List generateConfigItems(final ConfigBase configuration) { + destinationFile = VRScripts.IP_ALIAS_CONFIG; + + return super.generateConfigItems(configuration); + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/DhcpEntryConfigItem.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/DhcpEntryConfigItem.java new file mode 100644 index 0000000000..77d436d2f9 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/DhcpEntryConfigItem.java @@ -0,0 +1,49 @@ +// +// 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.agent.resource.virtualnetwork.facade; + +import java.util.List; + +import com.cloud.agent.api.routing.DhcpEntryCommand; +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.resource.virtualnetwork.ConfigItem; +import com.cloud.agent.resource.virtualnetwork.VRScripts; +import com.cloud.agent.resource.virtualnetwork.model.ConfigBase; +import com.cloud.agent.resource.virtualnetwork.model.VmDhcpConfig; + +public class DhcpEntryConfigItem extends AbstractConfigItemFacade { + + @Override + public List generateConfig(final NetworkElementCommand cmd) { + final DhcpEntryCommand command = (DhcpEntryCommand) cmd; + + final VmDhcpConfig vmDhcpConfig = new VmDhcpConfig(command.getVmName(), command.getVmMac(), command.getVmIpAddress(), command.getVmIp6Address(), command.getDuid(), command.getDefaultDns(), + command.getDefaultRouter(), command.getStaticRoutes(), command.isDefault()); + + return generateConfigItems(vmDhcpConfig); + } + + @Override + protected List generateConfigItems(final ConfigBase configuration) { + destinationFile = VRScripts.VM_DHCP_CONFIG; + + return super.generateConfigItems(configuration); + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/DnsMasqConfigItem.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/DnsMasqConfigItem.java new file mode 100644 index 0000000000..59dd9c695b --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/DnsMasqConfigItem.java @@ -0,0 +1,56 @@ +// +// 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.agent.resource.virtualnetwork.facade; + +import java.util.LinkedList; +import java.util.List; + +import com.cloud.agent.api.routing.DnsMasqConfigCommand; +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.api.to.DhcpTO; +import com.cloud.agent.resource.virtualnetwork.ConfigItem; +import com.cloud.agent.resource.virtualnetwork.VRScripts; +import com.cloud.agent.resource.virtualnetwork.model.ConfigBase; +import com.cloud.agent.resource.virtualnetwork.model.DhcpConfig; +import com.cloud.agent.resource.virtualnetwork.model.DhcpConfigEntry; + +public class DnsMasqConfigItem extends AbstractConfigItemFacade { + + @Override + public List generateConfig(final NetworkElementCommand cmd) { + final DnsMasqConfigCommand command = (DnsMasqConfigCommand) cmd; + + final LinkedList entries = new LinkedList(); + + for (final DhcpTO dhcpTo : command.getIps()) { + final DhcpConfigEntry entry = new DhcpConfigEntry(dhcpTo.getRouterIp(), dhcpTo.getGateway(), dhcpTo.getNetmask(), dhcpTo.getStartIpOfSubnet()); + entries.add(entry); + } + + return generateConfigItems(new DhcpConfig(entries)); + } + + @Override + protected List generateConfigItems(final ConfigBase configuration) { + destinationFile = VRScripts.DHCP_CONFIG; + + return super.generateConfigItems(configuration); + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/IpAssociationConfigItem.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/IpAssociationConfigItem.java new file mode 100644 index 0000000000..bd499053ff --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/IpAssociationConfigItem.java @@ -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. +// + +package com.cloud.agent.resource.virtualnetwork.facade; + +import java.util.LinkedList; +import java.util.List; + +import com.cloud.agent.api.routing.IpAssocCommand; +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.api.to.IpAddressTO; +import com.cloud.agent.resource.virtualnetwork.ConfigItem; +import com.cloud.agent.resource.virtualnetwork.VRScripts; +import com.cloud.agent.resource.virtualnetwork.model.ConfigBase; +import com.cloud.agent.resource.virtualnetwork.model.IpAddress; +import com.cloud.agent.resource.virtualnetwork.model.IpAssociation; + +public class IpAssociationConfigItem extends AbstractConfigItemFacade { + + @Override + public List generateConfig(final NetworkElementCommand cmd) { + final IpAssocCommand command = (IpAssocCommand) cmd; + + final List ips = new LinkedList(); + + for (final IpAddressTO ip : command.getIpAddresses()) { + final IpAddress ipAddress = new IpAddress(ip.getPublicIp(), ip.isSourceNat(), ip.isAdd(), ip.isOneToOneNat(), ip.isFirstIP(), ip.getVlanGateway(), ip.getVlanNetmask(), + ip.getVifMacAddress(), ip.getNicDevId(), ip.isNewNic()); + ips.add(ipAddress); + } + + final IpAssociation ipAssociation = new IpAssociation(ips.toArray(new IpAddress[ips.size()])); + + return generateConfigItems(ipAssociation); + } + + @Override + protected List generateConfigItems(final ConfigBase configuration) { + destinationFile = VRScripts.IP_ASSOCIATION_CONFIG; + + return super.generateConfigItems(configuration); + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/LoadBalancerConfigItem.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/LoadBalancerConfigItem.java new file mode 100644 index 0000000000..b943125d0a --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/LoadBalancerConfigItem.java @@ -0,0 +1,74 @@ +// +// 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.agent.resource.virtualnetwork.facade; + +import java.util.LinkedList; +import java.util.List; + +import com.cloud.agent.api.routing.LoadBalancerConfigCommand; +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.resource.virtualnetwork.ConfigItem; +import com.cloud.agent.resource.virtualnetwork.VRScripts; +import com.cloud.agent.resource.virtualnetwork.model.ConfigBase; +import com.cloud.agent.resource.virtualnetwork.model.LoadBalancerRule; +import com.cloud.agent.resource.virtualnetwork.model.LoadBalancerRules; +import com.cloud.network.HAProxyConfigurator; +import com.cloud.network.LoadBalancerConfigurator; + +public class LoadBalancerConfigItem extends AbstractConfigItemFacade { + + @Override + public List generateConfig(final NetworkElementCommand cmd) { + final LoadBalancerConfigCommand command = (LoadBalancerConfigCommand) cmd; + + final LoadBalancerConfigurator cfgtr = new HAProxyConfigurator(); + final String[] configuration = cfgtr.generateConfiguration(command); + + String routerIp = command.getNic().getIp(); + if (command.getVpcId() == null) { + routerIp = cmd.getAccessDetail(NetworkElementCommand.ROUTER_IP); + } + + final String tmpCfgFilePath = "/etc/haproxy/"; + final String tmpCfgFileName = "haproxy.cfg.new." + String.valueOf(System.currentTimeMillis()); + + final String[][] allRules = cfgtr.generateFwRules(command); + + final String[] addRules = allRules[LoadBalancerConfigurator.ADD]; + final String[] removeRules = allRules[LoadBalancerConfigurator.REMOVE]; + final String[] statRules = allRules[LoadBalancerConfigurator.STATS]; + + final LoadBalancerRule loadBalancerRule = new LoadBalancerRule(configuration, tmpCfgFilePath, tmpCfgFileName, addRules, removeRules, statRules, routerIp); + + final List rules = new LinkedList(); + rules.add(loadBalancerRule); + + final LoadBalancerRules configRules = new LoadBalancerRules(rules); + + return generateConfigItems(configRules); + } + + @Override + protected List generateConfigItems(final ConfigBase configuration) { + destinationFile = VRScripts.LOAD_BALANCER_CONFIG; + + return super.generateConfigItems(configuration); + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/RemoteAccessVpnConfigItem.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/RemoteAccessVpnConfigItem.java new file mode 100644 index 0000000000..be51c30745 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/RemoteAccessVpnConfigItem.java @@ -0,0 +1,48 @@ +// +// 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.agent.resource.virtualnetwork.facade; + +import java.util.List; + +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.api.routing.RemoteAccessVpnCfgCommand; +import com.cloud.agent.resource.virtualnetwork.ConfigItem; +import com.cloud.agent.resource.virtualnetwork.VRScripts; +import com.cloud.agent.resource.virtualnetwork.model.ConfigBase; +import com.cloud.agent.resource.virtualnetwork.model.RemoteAccessVpn; + +public class RemoteAccessVpnConfigItem extends AbstractConfigItemFacade { + + @Override + public List generateConfig(final NetworkElementCommand cmd) { + final RemoteAccessVpnCfgCommand command = (RemoteAccessVpnCfgCommand) cmd; + + final RemoteAccessVpn remoteAccessVpn = new RemoteAccessVpn(command.isCreate(), command.getIpRange(), command.getPresharedKey(), command.getVpnServerIp(), command.getLocalIp(), command.getLocalCidr(), + command.getPublicInterface()); + return generateConfigItems(remoteAccessVpn); + } + + @Override + protected List generateConfigItems(final ConfigBase configuration) { + destinationFile = VRScripts.REMOTE_ACCESS_VPN_CONFIG; + + return super.generateConfigItems(configuration); + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SavePasswordConfigItem.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SavePasswordConfigItem.java new file mode 100644 index 0000000000..4819fa989a --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SavePasswordConfigItem.java @@ -0,0 +1,47 @@ +// +// 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.agent.resource.virtualnetwork.facade; + +import java.util.List; + +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.api.routing.SavePasswordCommand; +import com.cloud.agent.resource.virtualnetwork.ConfigItem; +import com.cloud.agent.resource.virtualnetwork.VRScripts; +import com.cloud.agent.resource.virtualnetwork.model.ConfigBase; +import com.cloud.agent.resource.virtualnetwork.model.VmPassword; + +public class SavePasswordConfigItem extends AbstractConfigItemFacade { + + @Override + public List generateConfig(final NetworkElementCommand cmd) { + final SavePasswordCommand command = (SavePasswordCommand) cmd; + final VmPassword vmPassword = new VmPassword(command.getVmIpAddress(), command.getPassword()); + + return generateConfigItems(vmPassword); + } + + @Override + protected List generateConfigItems(final ConfigBase configuration) { + destinationFile = VRScripts.VM_PASSWORD_CONFIG; + + return super.generateConfigItems(configuration); + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetFirewallRulesConfigItem.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetFirewallRulesConfigItem.java new file mode 100644 index 0000000000..3327afa9eb --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetFirewallRulesConfigItem.java @@ -0,0 +1,58 @@ +// +// 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.agent.resource.virtualnetwork.facade; + +import java.util.ArrayList; +import java.util.List; + +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.api.routing.SetFirewallRulesCommand; +import com.cloud.agent.api.to.FirewallRuleTO; +import com.cloud.agent.resource.virtualnetwork.ConfigItem; +import com.cloud.agent.resource.virtualnetwork.VRScripts; +import com.cloud.agent.resource.virtualnetwork.model.ConfigBase; +import com.cloud.agent.resource.virtualnetwork.model.FirewallRule; +import com.cloud.agent.resource.virtualnetwork.model.FirewallRules; + +public class SetFirewallRulesConfigItem extends AbstractConfigItemFacade{ + + @Override + public List generateConfig(final NetworkElementCommand cmd) { + final SetFirewallRulesCommand command = (SetFirewallRulesCommand) cmd; + + final List rules = new ArrayList(); + for (final FirewallRuleTO rule : command.getRules()) { + final FirewallRule fwRule = new FirewallRule(rule.getId(), rule.getSrcVlanTag(), rule.getSrcIp(), rule.getProtocol(), rule.getSrcPortRange(), rule.revoked(), + rule.isAlreadyAdded(), rule.getSourceCidrList(), rule.getPurpose().toString(), rule.getIcmpType(), rule.getIcmpCode(), rule.getTrafficType().toString(), + rule.getGuestCidr(), rule.isDefaultEgressPolicy()); + rules.add(fwRule); + } + + final FirewallRules ruleSet = new FirewallRules(rules.toArray(new FirewallRule[rules.size()])); + return generateConfigItems(ruleSet); + } + + @Override + protected List generateConfigItems(final ConfigBase configuration) { + destinationFile = VRScripts.FIREWALL_RULES_CONFIG; + + return super.generateConfigItems(configuration); + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetGuestNetworkConfigItem.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetGuestNetworkConfigItem.java new file mode 100644 index 0000000000..f96e6d47d2 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetGuestNetworkConfigItem.java @@ -0,0 +1,68 @@ +// +// 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.agent.resource.virtualnetwork.facade; + +import java.util.List; + +import com.cloud.agent.api.SetupGuestNetworkCommand; +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.api.to.NicTO; +import com.cloud.agent.resource.virtualnetwork.ConfigItem; +import com.cloud.agent.resource.virtualnetwork.VRScripts; +import com.cloud.agent.resource.virtualnetwork.model.ConfigBase; +import com.cloud.agent.resource.virtualnetwork.model.GuestNetwork; +import com.cloud.utils.net.NetUtils; + +public class SetGuestNetworkConfigItem extends AbstractConfigItemFacade { + + @Override + public List generateConfig(final NetworkElementCommand cmd) { + final SetupGuestNetworkCommand command = (SetupGuestNetworkCommand) cmd; + + final NicTO nic = command.getNic(); + final String routerGIP = command.getAccessDetail(NetworkElementCommand.ROUTER_GUEST_IP); + final String gateway = command.getAccessDetail(NetworkElementCommand.GUEST_NETWORK_GATEWAY); + final String cidr = Long.toString(NetUtils.getCidrSize(nic.getNetmask())); + final String netmask = nic.getNetmask(); + final String domainName = command.getNetworkDomain(); + String dns = command.getDefaultDns1(); + + if (dns == null || dns.isEmpty()) { + dns = command.getDefaultDns2(); + } else { + final String dns2 = command.getDefaultDns2(); + if (dns2 != null && !dns2.isEmpty()) { + dns += "," + dns2; + } + } + + final GuestNetwork guestNetwork = new GuestNetwork(command.isAdd(), nic.getMac(), "eth" + nic.getDeviceId(), routerGIP, netmask, gateway, + cidr, dns, domainName); + + return generateConfigItems(guestNetwork); + } + + @Override + protected List generateConfigItems(final ConfigBase configuration) { + destinationFile = VRScripts.GUEST_NETWORK_CONFIG; + + return super.generateConfigItems(configuration); + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetMonitorServiceConfigItem.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetMonitorServiceConfigItem.java new file mode 100644 index 0000000000..2cf03e445f --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetMonitorServiceConfigItem.java @@ -0,0 +1,47 @@ +// +// 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.agent.resource.virtualnetwork.facade; + +import java.util.List; + +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.api.routing.SetMonitorServiceCommand; +import com.cloud.agent.resource.virtualnetwork.ConfigItem; +import com.cloud.agent.resource.virtualnetwork.VRScripts; +import com.cloud.agent.resource.virtualnetwork.model.ConfigBase; +import com.cloud.agent.resource.virtualnetwork.model.MonitorService; + +public class SetMonitorServiceConfigItem extends AbstractConfigItemFacade { + + @Override + public List generateConfig(final NetworkElementCommand cmd) { + final SetMonitorServiceCommand command = (SetMonitorServiceCommand) cmd; + + final MonitorService monitorService = new MonitorService(command.getConfiguration(), cmd.getAccessDetail(NetworkElementCommand.ROUTER_MONITORING_ENABLE)); + return generateConfigItems(monitorService); + } + + @Override + protected List generateConfigItems(final ConfigBase configuration) { + destinationFile = VRScripts.MONITOR_SERVICE_CONFIG; + + return super.generateConfigItems(configuration); + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetNetworkAclConfigItem.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetNetworkAclConfigItem.java new file mode 100644 index 0000000000..6e0012daca --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetNetworkAclConfigItem.java @@ -0,0 +1,108 @@ +// +// 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.agent.resource.virtualnetwork.facade; + +import java.util.ArrayList; +import java.util.List; + +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.api.routing.SetNetworkACLCommand; +import com.cloud.agent.api.to.NicTO; +import com.cloud.agent.resource.virtualnetwork.ConfigItem; +import com.cloud.agent.resource.virtualnetwork.VRScripts; +import com.cloud.agent.resource.virtualnetwork.model.AclRule; +import com.cloud.agent.resource.virtualnetwork.model.AllAclRule; +import com.cloud.agent.resource.virtualnetwork.model.ConfigBase; +import com.cloud.agent.resource.virtualnetwork.model.IcmpAclRule; +import com.cloud.agent.resource.virtualnetwork.model.NetworkACL; +import com.cloud.agent.resource.virtualnetwork.model.ProtocolAclRule; +import com.cloud.agent.resource.virtualnetwork.model.TcpAclRule; +import com.cloud.agent.resource.virtualnetwork.model.UdpAclRule; +import com.cloud.utils.net.NetUtils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SetNetworkAclConfigItem extends AbstractConfigItemFacade { + + public static final Logger s_logger = LoggerFactory.getLogger(SetNetworkAclConfigItem.class.getName()); + + @Override + public List generateConfig(final NetworkElementCommand cmd) { + final SetNetworkACLCommand command = (SetNetworkACLCommand) cmd; + + final String privateGw = cmd.getAccessDetail(NetworkElementCommand.VPC_PRIVATE_GATEWAY); + + final String[][] rules = command.generateFwRules(); + final String[] aclRules = rules[0]; + final NicTO nic = command.getNic(); + final String dev = "eth" + nic.getDeviceId(); + final String netmask = Long.toString(NetUtils.getCidrSize(nic.getNetmask())); + + final List ingressRules = new ArrayList(); + final List egressRules = new ArrayList(); + + for (int i = 0; i < aclRules.length; i++) { + AclRule aclRule; + final String[] ruleParts = aclRules[i].split(":"); + switch (ruleParts[1].toLowerCase()) { + case "icmp": + aclRule = new IcmpAclRule(ruleParts[4], "ACCEPT".equals(ruleParts[5]), Integer.parseInt(ruleParts[2]), Integer.parseInt(ruleParts[3])); + break; + case "tcp": + aclRule = new TcpAclRule(ruleParts[4], "ACCEPT".equals(ruleParts[5]), Integer.parseInt(ruleParts[2]), Integer.parseInt(ruleParts[3])); + break; + case "udp": + aclRule = new UdpAclRule(ruleParts[4], "ACCEPT".equals(ruleParts[5]), Integer.parseInt(ruleParts[2]), Integer.parseInt(ruleParts[3])); + break; + case "all": + aclRule = new AllAclRule(ruleParts[4], "ACCEPT".equals(ruleParts[5])); + break; + default: + // Fuzzy logic in cloudstack: if we do not handle it here, it will throw an exception and work okay (with a stack trace on the console). + // If we check the size of the array, it will fail to setup the network. + // So, let's catch the exception and continue in the loop. + try { + aclRule = new ProtocolAclRule(ruleParts[5], false, Integer.parseInt(ruleParts[1])); + } catch (final Exception e) { + s_logger.warn("Problem occured when reading the entries in the ruleParts array. Actual array size is '" + ruleParts.length + "', but trying to read from index 5."); + continue; + } + } + if ("Ingress".equals(ruleParts[0])) { + ingressRules.add(aclRule); + } else { + egressRules.add(aclRule); + } + } + + final NetworkACL networkACL = new NetworkACL(dev, nic.getMac(), privateGw != null, nic.getIp(), netmask, ingressRules.toArray(new AclRule[ingressRules.size()]), + egressRules.toArray(new AclRule[egressRules.size()])); + + return generateConfigItems(networkACL); + } + + @Override + protected List generateConfigItems(final ConfigBase configuration) { + destinationFile = VRScripts.NETWORK_ACL_CONFIG; + + return super.generateConfigItems(configuration); + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetPortForwardingRulesConfigItem.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetPortForwardingRulesConfigItem.java new file mode 100644 index 0000000000..0248ffe48e --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetPortForwardingRulesConfigItem.java @@ -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. +// + +package com.cloud.agent.resource.virtualnetwork.facade; + +import java.util.ArrayList; +import java.util.List; + +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.api.routing.SetPortForwardingRulesCommand; +import com.cloud.agent.api.to.PortForwardingRuleTO; +import com.cloud.agent.resource.virtualnetwork.ConfigItem; +import com.cloud.agent.resource.virtualnetwork.VRScripts; +import com.cloud.agent.resource.virtualnetwork.model.ConfigBase; +import com.cloud.agent.resource.virtualnetwork.model.ForwardingRule; +import com.cloud.agent.resource.virtualnetwork.model.ForwardingRules; + +public class SetPortForwardingRulesConfigItem extends AbstractConfigItemFacade { + + @Override + public List generateConfig(final NetworkElementCommand cmd) { + final SetPortForwardingRulesCommand command = (SetPortForwardingRulesCommand) cmd; + + final List rules = new ArrayList(); + + for (final PortForwardingRuleTO rule : command.getRules()) { + final ForwardingRule fwdRule = new ForwardingRule(rule.revoked(), rule.getProtocol().toLowerCase(), rule.getSrcIp(), rule.getStringSrcPortRange(), rule.getDstIp(), + rule.getStringDstPortRange()); + rules.add(fwdRule); + } + + final ForwardingRules ruleSet = new ForwardingRules(rules.toArray(new ForwardingRule[rules.size()])); + + return generateConfigItems(ruleSet); + } + + @Override + protected List generateConfigItems(final ConfigBase configuration) { + destinationFile = VRScripts.FORWARDING_RULES_CONFIG; + + return super.generateConfigItems(configuration); + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetPortForwardingRulesVpcConfigItem.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetPortForwardingRulesVpcConfigItem.java new file mode 100644 index 0000000000..7a8b609bb8 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetPortForwardingRulesVpcConfigItem.java @@ -0,0 +1,33 @@ +// +// 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.agent.resource.virtualnetwork.facade; + +import java.util.List; + +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.resource.virtualnetwork.ConfigItem; + +public class SetPortForwardingRulesVpcConfigItem extends SetPortForwardingRulesConfigItem { + + @Override + public List generateConfig(final NetworkElementCommand cmd) { + return super.generateConfig(cmd); + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetSourceNatConfigItem.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetSourceNatConfigItem.java new file mode 100644 index 0000000000..cb6989ea2a --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetSourceNatConfigItem.java @@ -0,0 +1,54 @@ +// +// 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.agent.resource.virtualnetwork.facade; + +import java.util.LinkedList; +import java.util.List; + +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.resource.virtualnetwork.ConfigItem; +import com.cloud.agent.resource.virtualnetwork.model.ConfigBase; + +public class SetSourceNatConfigItem extends AbstractConfigItemFacade { + + @Override + public List generateConfig(final NetworkElementCommand cmd) { + final LinkedList cfg = new LinkedList<>(); + + /* FIXME This seems useless as we already pass this info with the ipassoc + * SetSourceNatCommand command = (SetSourceNatCommand) cmd; + * IpAddressTO pubIP = command.getIpAddress(); + * String dev = "eth" + pubIP.getNicDevId(); + * String args = "-A"; + * args += " -l "; + * args += pubIP.getPublicIp(); + * args += " -c "; + * args += dev; + * cfg.add(new ScriptConfigItem(VRScripts.VPC_SOURCE_NAT, args)); + */ + + return cfg; + } + + @Override + protected List generateConfigItems(final ConfigBase configuration) { + return null; + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetStaticNatRulesConfigItem.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetStaticNatRulesConfigItem.java new file mode 100644 index 0000000000..027979e302 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetStaticNatRulesConfigItem.java @@ -0,0 +1,56 @@ +// +// 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.agent.resource.virtualnetwork.facade; + +import java.util.LinkedList; +import java.util.List; + +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.api.routing.SetStaticNatRulesCommand; +import com.cloud.agent.api.to.StaticNatRuleTO; +import com.cloud.agent.resource.virtualnetwork.ConfigItem; +import com.cloud.agent.resource.virtualnetwork.VRScripts; +import com.cloud.agent.resource.virtualnetwork.model.ConfigBase; +import com.cloud.agent.resource.virtualnetwork.model.StaticNatRule; +import com.cloud.agent.resource.virtualnetwork.model.StaticNatRules; + +public class SetStaticNatRulesConfigItem extends AbstractConfigItemFacade { + + @Override + public List generateConfig(final NetworkElementCommand cmd) { + final SetStaticNatRulesCommand command = (SetStaticNatRulesCommand) cmd; + + final LinkedList rules = new LinkedList<>(); + for (final StaticNatRuleTO rule : command.getRules()) { + final StaticNatRule staticNatRule = new StaticNatRule(rule.revoked(), rule.getProtocol(), rule.getSrcIp(), rule.getStringSrcPortRange(), rule.getDstIp()); + rules.add(staticNatRule); + } + final StaticNatRules staticNatRules = new StaticNatRules(rules); + + return generateConfigItems(staticNatRules); + } + + @Override + protected List generateConfigItems(final ConfigBase configuration) { + destinationFile = VRScripts.STATICNAT_RULES_CONFIG; + + return super.generateConfigItems(configuration); + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetStaticRouteConfigItem.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetStaticRouteConfigItem.java new file mode 100644 index 0000000000..76630e0b87 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetStaticRouteConfigItem.java @@ -0,0 +1,57 @@ +// +// 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.agent.resource.virtualnetwork.facade; + +import java.util.LinkedList; +import java.util.List; + +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.api.routing.SetStaticRouteCommand; +import com.cloud.agent.resource.virtualnetwork.ConfigItem; +import com.cloud.agent.resource.virtualnetwork.VRScripts; +import com.cloud.agent.resource.virtualnetwork.model.ConfigBase; +import com.cloud.agent.resource.virtualnetwork.model.StaticRoute; +import com.cloud.agent.resource.virtualnetwork.model.StaticRoutes; +import com.cloud.network.vpc.StaticRouteProfile; + +public class SetStaticRouteConfigItem extends AbstractConfigItemFacade { + + @Override + public List generateConfig(final NetworkElementCommand cmd) { + final SetStaticRouteCommand command = (SetStaticRouteCommand) cmd; + + final LinkedList routes = new LinkedList<>(); + + for (final StaticRouteProfile profile : command.getStaticRoutes()) { + final boolean keep = profile.getState() == com.cloud.network.vpc.StaticRoute.State.Active || profile.getState() == com.cloud.network.vpc.StaticRoute.State.Add; + + routes.add(new StaticRoute(!keep, profile.getIp4Address(), profile.getCidr())); + } + + return generateConfigItems(new StaticRoutes(routes)); + } + + @Override + protected List generateConfigItems(final ConfigBase configuration) { + destinationFile = VRScripts.STATIC_ROUTES_CONFIG; + + return super.generateConfigItems(configuration); + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/Site2SiteVpnConfigItem.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/Site2SiteVpnConfigItem.java new file mode 100644 index 0000000000..5bb466c593 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/Site2SiteVpnConfigItem.java @@ -0,0 +1,49 @@ +// +// 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.agent.resource.virtualnetwork.facade; + +import java.util.List; + +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.api.routing.Site2SiteVpnCfgCommand; +import com.cloud.agent.resource.virtualnetwork.ConfigItem; +import com.cloud.agent.resource.virtualnetwork.VRScripts; +import com.cloud.agent.resource.virtualnetwork.model.ConfigBase; +import com.cloud.agent.resource.virtualnetwork.model.Site2SiteVpn; + +public class Site2SiteVpnConfigItem extends AbstractConfigItemFacade { + + @Override + public List generateConfig(final NetworkElementCommand cmd) { + final Site2SiteVpnCfgCommand command = (Site2SiteVpnCfgCommand) cmd; + + final Site2SiteVpn site2siteVpn = new Site2SiteVpn(command.getLocalPublicIp(), command.getLocalGuestCidr(), command.getLocalPublicGateway(), command.getPeerGatewayIp(), + command.getPeerGuestCidrList(), command.getEspPolicy(), command.getIkePolicy(), command.getIpsecPsk(), command.getIkeLifetime(), command.getEspLifetime(), command.isCreate(), command.getDpd(), + command.isPassive(), command.getEncap()); + return generateConfigItems(site2siteVpn); + } + + @Override + protected List generateConfigItems(final ConfigBase configuration) { + destinationFile = VRScripts.SITE_2_SITE_VPN_CONFIG; + + return super.generateConfigItems(configuration); + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/VmDataConfigItem.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/VmDataConfigItem.java new file mode 100644 index 0000000000..9a3fb36241 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/VmDataConfigItem.java @@ -0,0 +1,48 @@ +// +// 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.agent.resource.virtualnetwork.facade; + +import java.util.List; + +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.api.routing.VmDataCommand; +import com.cloud.agent.resource.virtualnetwork.ConfigItem; +import com.cloud.agent.resource.virtualnetwork.VRScripts; +import com.cloud.agent.resource.virtualnetwork.model.ConfigBase; +import com.cloud.agent.resource.virtualnetwork.model.VmData; + +public class VmDataConfigItem extends AbstractConfigItemFacade { + + @Override + public List generateConfig(final NetworkElementCommand cmd) { + final VmDataCommand command = (VmDataCommand) cmd; + + final VmData vmData = new VmData(command.getVmIpAddress(), command.getVmData()); + + return generateConfigItems(vmData); + } + + @Override + protected List generateConfigItems(final ConfigBase configuration) { + destinationFile = VRScripts.VM_METADATA_CONFIG; + + return super.generateConfigItems(configuration); + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/VpnUsersConfigItem.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/VpnUsersConfigItem.java new file mode 100644 index 0000000000..c98a93e2d3 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/VpnUsersConfigItem.java @@ -0,0 +1,54 @@ +// +// 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.agent.resource.virtualnetwork.facade; + +import java.util.LinkedList; +import java.util.List; + +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.api.routing.VpnUsersCfgCommand; +import com.cloud.agent.resource.virtualnetwork.ConfigItem; +import com.cloud.agent.resource.virtualnetwork.VRScripts; +import com.cloud.agent.resource.virtualnetwork.model.ConfigBase; +import com.cloud.agent.resource.virtualnetwork.model.VpnUser; +import com.cloud.agent.resource.virtualnetwork.model.VpnUserList; + +public class VpnUsersConfigItem extends AbstractConfigItemFacade { + + @Override + public List generateConfig(final NetworkElementCommand cmd) { + final VpnUsersCfgCommand command = (VpnUsersCfgCommand) cmd; + + final List vpnUsers = new LinkedList(); + for (final VpnUsersCfgCommand.UsernamePassword userpwd : command.getUserpwds()) { + vpnUsers.add(new VpnUser(userpwd.getUsername(), userpwd.getPassword(), userpwd.isAdd())); + } + + final VpnUserList vpnUserList = new VpnUserList(vpnUsers); + return generateConfigItems(vpnUserList); + } + + @Override + protected List generateConfigItems(final ConfigBase configuration) { + destinationFile = VRScripts.VPN_USER_LIST_CONFIG; + + return super.generateConfigItems(configuration); + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/AclRule.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/AclRule.java new file mode 100644 index 0000000000..520fc661a0 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/AclRule.java @@ -0,0 +1,60 @@ +// +// 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.agent.resource.virtualnetwork.model; + +public abstract class AclRule { + private String cidr; + private boolean allowed; + + public String getCidr() { + return cidr; + } + + public void setCidr(String cidr) { + this.cidr = cidr; + } + + public boolean isAllowed() { + return allowed; + } + + public void setAllowed(boolean allowed) { + this.allowed = allowed; + } + + protected AclRule() { + // Empty constructor for (de)serialization + } + + protected AclRule(String cidr, boolean allowed) { + this.cidr = cidr; + this.allowed = allowed; + } + +} + +/* +{"device":"eth2","mac_address":"02:00:56:36:00:02","private_gateway_acl":false,"nic_ip":"172.16.1.1","nic_netmask":"24", + "rule":"Ingress:41:0:0:192.168.5.0/24:DROP:," + + "Ingress:all:0:0:192.168.4.0/24:ACCEPT:," + + "Ingress:icmp:8:-1:192.168.3.0/24:ACCEPT:," + + "Ingress:udp:8080:8081:192.168.2.0/24:ACCEPT:," + + "Ingress:tcp:22:22:192.168.1.0/24:ACCEPT:,","type":"networkacl"} + */ \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/AllAclRule.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/AllAclRule.java new file mode 100644 index 0000000000..0f43450c23 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/AllAclRule.java @@ -0,0 +1,33 @@ +// +// 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.agent.resource.virtualnetwork.model; + +public class AllAclRule extends AclRule { + private final String type = "all"; + + public AllAclRule() { + // Empty constructor for (de)serialization + } + + public AllAclRule(String cidr, boolean allowed) { + super(cidr, allowed); + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ConfigBase.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ConfigBase.java new file mode 100644 index 0000000000..edc721178c --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ConfigBase.java @@ -0,0 +1,60 @@ +// +// 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.agent.resource.virtualnetwork.model; + +public abstract class ConfigBase { + public final static String UNKNOWN = "unknown"; + public final static String VM_DHCP = "dhcpentry"; + public final static String IP_ASSOCIATION = "ips"; + public final static String GUEST_NETWORK = "guestnetwork"; + public static final String NETWORK_ACL = "networkacl"; + public static final String VM_METADATA = "vmdata"; + public static final String VM_PASSWORD = "vmpassword"; + public static final String FORWARDING_RULES = "forwardrules"; + public static final String FIREWALL_RULES = "firewallrules"; + public static final String VPN_USER_LIST = "vpnuserlist"; + public static final String STATICNAT_RULES = "staticnatrules"; + public static final String IP_ALIAS_CONFIG = "ipaliases"; + public static final String SITE2SITEVPN = "site2sitevpn"; + public static final String STATIC_ROUTES = "staticroutes"; + public static final String REMOTEACCESSVPN = "remoteaccessvpn"; + public static final String MONITORSERVICE = "monitorservice"; + public static final String DHCP_CONFIG = "dhcpconfig"; + public static final String LOAD_BALANCER = "loadbalancer"; + + private String type = UNKNOWN; + + private ConfigBase() { + // Empty constructor for (de)serialization + } + + protected ConfigBase(final String type) { + this.type = type; + } + + public String getType() { + return type; + } + + public void setType(final String type) { + this.type = type; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/DhcpConfig.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/DhcpConfig.java new file mode 100644 index 0000000000..b1a83a394d --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/DhcpConfig.java @@ -0,0 +1,45 @@ +// +// 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.agent.resource.virtualnetwork.model; + +import java.util.LinkedList; +import java.util.List; + +public class DhcpConfig extends ConfigBase { + List entries = new LinkedList(); + + public DhcpConfig() { + super(ConfigBase.DHCP_CONFIG); + } + + public DhcpConfig(List entries) { + super(ConfigBase.DHCP_CONFIG); + this.entries = entries; + } + + public List getEntries() { + return entries; + } + + public void setEntries(List entries) { + this.entries = entries; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/DhcpConfigEntry.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/DhcpConfigEntry.java new file mode 100644 index 0000000000..6bd1521f0d --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/DhcpConfigEntry.java @@ -0,0 +1,72 @@ +// +// 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.agent.resource.virtualnetwork.model; + +public class DhcpConfigEntry { + private String routerIpAddress; + private String gateway; + private String netmask; + private String firstIpOfSubnet; + + public DhcpConfigEntry() { + // Empty for (de)serialization + } + + public DhcpConfigEntry(String routerIpAddress, String gateway, String netmask, String firstIpOfSubnet) { + super(); + this.routerIpAddress = routerIpAddress; + this.gateway = gateway; + this.netmask = netmask; + this.firstIpOfSubnet = firstIpOfSubnet; + } + + public String getRouterIpAddress() { + return routerIpAddress; + } + + public void setRouterIpAddress(String routerIpAddress) { + this.routerIpAddress = routerIpAddress; + } + + public String getGateway() { + return gateway; + } + + public void setGateway(String gateway) { + this.gateway = gateway; + } + + public String getNetmask() { + return netmask; + } + + public void setNetmask(String netmask) { + this.netmask = netmask; + } + + public String getFirstIpOfSubnet() { + return firstIpOfSubnet; + } + + public void setFirstIpOfSubnet(String firstIpOfSubnet) { + this.firstIpOfSubnet = firstIpOfSubnet; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/FirewallRule.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/FirewallRule.java new file mode 100644 index 0000000000..0543094795 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/FirewallRule.java @@ -0,0 +1,175 @@ +// +// 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.agent.resource.virtualnetwork.model; + +import java.util.List; + +public class FirewallRule { + private long id; + private String srcVlanTag; + private String srcIp; + private String protocol; + private int[] srcPortRange; + private boolean revoked; + private boolean alreadyAdded; + private List sourceCidrList; + private String purpose; + private Integer icmpType; + private Integer icmpCode; + private String trafficType; + private String guestCidr; + private boolean defaultEgressPolicy; + private String type; + + public FirewallRule() { + // Empty constructor for (de)serialization + } + + public FirewallRule(long id, String srcVlanTag, String srcIp, String protocol, int[] srcPortRange, boolean revoked, boolean alreadyAdded, List sourceCidrList, + String purpose, Integer icmpType, Integer icmpCode, String trafficType, String guestCidr, boolean defaultEgressPolicy) { + this.id = id; + this.srcVlanTag = srcVlanTag; + this.srcIp = srcIp; + this.protocol = protocol; + this.srcPortRange = srcPortRange; + this.revoked = revoked; + this.alreadyAdded = alreadyAdded; + this.sourceCidrList = sourceCidrList; + this.purpose = purpose; + this.icmpType = icmpType; + this.icmpCode = icmpCode; + this.trafficType = trafficType; + this.guestCidr = guestCidr; + this.defaultEgressPolicy = defaultEgressPolicy; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getSrcVlanTag() { + return srcVlanTag; + } + + public void setSrcVlanTag(String srcVlanTag) { + this.srcVlanTag = srcVlanTag; + } + + public String getSrcIp() { + return srcIp; + } + + public void setSrcIp(String srcIp) { + this.srcIp = srcIp; + } + + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public int[] getSrcPortRange() { + return srcPortRange; + } + + public void setSrcPortRange(int[] srcPortRange) { + this.srcPortRange = srcPortRange; + } + + public boolean isRevoked() { + return revoked; + } + + public void setRevoked(boolean revoked) { + this.revoked = revoked; + } + + public boolean isAlreadyAdded() { + return alreadyAdded; + } + + public void setAlreadyAdded(boolean alreadyAdded) { + this.alreadyAdded = alreadyAdded; + } + + public List getSourceCidrList() { + return sourceCidrList; + } + + public void setSourceCidrList(List sourceCidrList) { + this.sourceCidrList = sourceCidrList; + } + + public String getPurpose() { + return purpose; + } + + public void setPurpose(String purpose) { + this.purpose = purpose; + } + + public Integer getIcmpType() { + return icmpType; + } + + public void setIcmpType(Integer icmpType) { + this.icmpType = icmpType; + } + + public Integer getIcmpCode() { + return icmpCode; + } + + public void setIcmpCode(Integer icmpCode) { + this.icmpCode = icmpCode; + } + + public String getTrafficType() { + return trafficType; + } + + public void setTrafficType(String trafficType) { + this.trafficType = trafficType; + } + + public String getGuestCidr() { + return guestCidr; + } + + public void setGuestCidr(String guestCidr) { + this.guestCidr = guestCidr; + } + + public boolean isDefaultEgressPolicy() { + return defaultEgressPolicy; + } + + public void setDefaultEgressPolicy(boolean defaultEgressPolicy) { + this.defaultEgressPolicy = defaultEgressPolicy; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/FirewallRules.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/FirewallRules.java new file mode 100644 index 0000000000..4a0d814131 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/FirewallRules.java @@ -0,0 +1,42 @@ +// +// 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.agent.resource.virtualnetwork.model; + +public class FirewallRules extends ConfigBase { + FirewallRule[] rules; + + public FirewallRules() { + super(ConfigBase.FIREWALL_RULES); + } + + public FirewallRules(FirewallRule[] rules) { + super(ConfigBase.FIREWALL_RULES); + this.rules = rules; + } + + public FirewallRule[] getRules() { + return rules; + } + + public void setRules(FirewallRule[] rules) { + this.rules = rules; + } + +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ForwardingRule.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ForwardingRule.java new file mode 100644 index 0000000000..cf3e43d1c0 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ForwardingRule.java @@ -0,0 +1,91 @@ +// +// 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.agent.resource.virtualnetwork.model; + +public class ForwardingRule { + private boolean revoke; + private String protocol; + private String sourceIpAddress; + private String sourcePortRange; + private String destinationIpAddress; + private String destinationPortRange; + + public ForwardingRule() { + // Empty constructor for (de)serialization + } + + public ForwardingRule(boolean revoke, String protocol, String sourceIpAddress, String sourcePortRange, String destinationIpAddress, String destinationPortRange) { + this.revoke = revoke; + this.protocol = protocol; + this.sourceIpAddress = sourceIpAddress; + this.sourcePortRange = sourcePortRange; + this.destinationIpAddress = destinationIpAddress; + this.destinationPortRange = destinationPortRange; + } + + public boolean isRevoke() { + return revoke; + } + + public void setRevoke(boolean revoke) { + this.revoke = revoke; + } + + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public String getSourceIpAddress() { + return sourceIpAddress; + } + + public void setSourceIpAddress(String sourceIpAddress) { + this.sourceIpAddress = sourceIpAddress; + } + + public String getSourcePortRange() { + return sourcePortRange; + } + + public void setSourcePortRange(String sourcePortRange) { + this.sourcePortRange = sourcePortRange; + } + + public String getDestinationIpAddress() { + return destinationIpAddress; + } + + public void setDestinationIpAddress(String destinationIpAddress) { + this.destinationIpAddress = destinationIpAddress; + } + + public String getDestinationPortRange() { + return destinationPortRange; + } + + public void setDestinationPortRange(String destinationPortRange) { + this.destinationPortRange = destinationPortRange; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ForwardingRules.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ForwardingRules.java new file mode 100644 index 0000000000..0ba001d045 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ForwardingRules.java @@ -0,0 +1,42 @@ +// +// 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.agent.resource.virtualnetwork.model; + +public class ForwardingRules extends ConfigBase { + ForwardingRule[] rules; + + public ForwardingRules() { + super(ConfigBase.FORWARDING_RULES); + } + + public ForwardingRules(ForwardingRule[] rules) { + super(ConfigBase.FORWARDING_RULES); + this.rules = rules; + } + + public ForwardingRule[] getRules() { + return rules; + } + + public void setRules(ForwardingRule[] rules) { + this.rules = rules; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/GuestNetwork.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/GuestNetwork.java new file mode 100644 index 0000000000..076073e794 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/GuestNetwork.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.agent.resource.virtualnetwork.model; + +public class GuestNetwork extends ConfigBase { + private boolean add; + private String macAddress; + private String device; + private String routerGuestIp; + private String routerGuestNetmask; + private String routerGuestGateway; + private String cidr; + private String dns; + private String domainName; + + public GuestNetwork() { + super(ConfigBase.GUEST_NETWORK); + } + + public GuestNetwork(final boolean add, final String macAddress, final String device, final String routerGuestIp, final String routerGuestNetmask, final String routerGuestGateway, + final String cidr, final String dns, final String domainName) { + super(ConfigBase.GUEST_NETWORK); + this.add = add; + this.macAddress = macAddress; + this.device = device; + this.routerGuestIp = routerGuestIp; + this.routerGuestNetmask = routerGuestNetmask; + this.routerGuestGateway = routerGuestGateway; + this.cidr = cidr; + this.dns = dns; + this.domainName = domainName; + } + + public boolean isAdd() { + return add; + } + + public void setAdd(final boolean add) { + this.add = add; + } + + public String getMacAddress() { + return macAddress; + } + + public void setMacAddress(final String macAddress) { + this.macAddress = macAddress; + } + + public String getDevice() { + return device; + } + + public void setDevice(final String device) { + this.device = device; + } + + public String getRouterGuestIp() { + return routerGuestIp; + } + + public void setRouterGuestIp(final String routerGuestIp) { + this.routerGuestIp = routerGuestIp; + } + + public String getRouterGuestNetmask() { + return routerGuestNetmask; + } + + public void setRouterGuestNetmask(final String routerGuestNetmask) { + this.routerGuestNetmask = routerGuestNetmask; + } + + public String getRouterGuestGateway() { + return routerGuestGateway; + } + + public void setRouterGuestGateway(final String routerGuestGateway) { + this.routerGuestGateway = routerGuestGateway; + } + + public String getCidr() { + return cidr; + } + + public void setCidr(final String cidr) { + this.cidr = cidr; + } + + public String getDns() { + return dns; + } + + public void setDns(final String dns) { + this.dns = dns; + } + + public String getDomainName() { + return domainName; + } + + public void setDomainName(final String domainName) { + this.domainName = domainName; + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/IcmpAclRule.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/IcmpAclRule.java new file mode 100644 index 0000000000..f523f1eb41 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/IcmpAclRule.java @@ -0,0 +1,53 @@ +// +// 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.agent.resource.virtualnetwork.model; + +public class IcmpAclRule extends AclRule { + private final String type = "icmp"; + private int icmpType; + private int icmpCode; + + public IcmpAclRule() { + // Empty constructor for (de)serialization + } + + public IcmpAclRule(String cidr, boolean allowed, int icmpType, int icmpCode) { + super(cidr, allowed); + this.icmpType = icmpType; + this.icmpCode = icmpCode; + } + + public int getIcmpType() { + return icmpType; + } + + public void setIcmpType(int icmpType) { + this.icmpType = icmpType; + } + + public int getIcmpCode() { + return icmpCode; + } + + public void setIcmpCode(int icmpCode) { + this.icmpCode = icmpCode; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/IpAddress.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/IpAddress.java new file mode 100644 index 0000000000..5889bd28ea --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/IpAddress.java @@ -0,0 +1,134 @@ +// +// 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.agent.resource.virtualnetwork.model; + + +public class IpAddress { + private String publicIp; + private boolean sourceNat; + private boolean add; + private boolean oneToOneNat; + private boolean firstIP; + private String gateway; + private String netmask; + private String vifMacAddress; + private Integer nicDevId; + private boolean newNic; + + public IpAddress() { + // Empty constructor for (de)serialization + } + + public IpAddress(String publicIp, boolean sourceNat, boolean add, boolean oneToOneNat, boolean firstIP, String gateway, String netmask, String vifMacAddress, + Integer nicDevId, boolean newNic) { + super(); + this.publicIp = publicIp; + this.sourceNat = sourceNat; + this.add = add; + this.oneToOneNat = oneToOneNat; + this.firstIP = firstIP; + this.gateway = gateway; + this.netmask = netmask; + this.vifMacAddress = vifMacAddress; + this.nicDevId = nicDevId; + this.newNic = newNic; + } + + public String getPublicIp() { + return publicIp; + } + + public void setPublicIp(String publicIp) { + this.publicIp = publicIp; + } + + public boolean isSourceNat() { + return sourceNat; + } + + public void setSourceNat(boolean sourceNat) { + this.sourceNat = sourceNat; + } + + public boolean isAdd() { + return add; + } + + public void setAdd(boolean add) { + this.add = add; + } + + public boolean isOneToOneNat() { + return oneToOneNat; + } + + public void setOneToOneNat(boolean oneToOneNat) { + this.oneToOneNat = oneToOneNat; + } + + public boolean isFirstIP() { + return firstIP; + } + + public void setFirstIP(boolean firstIP) { + this.firstIP = firstIP; + } + + public String getGateway() { + return gateway; + } + + public void setGateway(String gateway) { + this.gateway = gateway; + } + + public String getNetmask() { + return netmask; + } + + public void setNetmask(String netmask) { + this.netmask = netmask; + } + + public String getVifMacAddress() { + return vifMacAddress; + } + + public void setVifMacAddress(String vifMacAddress) { + this.vifMacAddress = vifMacAddress; + } + + public Integer getNicDevId() { + return nicDevId; + } + + public void setNicDevId(Integer nicDevId) { + this.nicDevId = nicDevId; + } + + public boolean isNewNic() { + return newNic; + } + + public void setNewNic(boolean newNic) { + this.newNic = newNic; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/IpAddressAlias.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/IpAddressAlias.java new file mode 100644 index 0000000000..f3b3e833af --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/IpAddressAlias.java @@ -0,0 +1,72 @@ +// +// 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.agent.resource.virtualnetwork.model; + +public class IpAddressAlias { + private boolean revoke; + private String IpAddress; + private String netmask; + private long count; + + public IpAddressAlias() { + // Empty constructor for (de)serialization + } + + public IpAddressAlias(boolean revoke, String ipAddress, String netmask, long count) { + super(); + this.revoke = revoke; + IpAddress = ipAddress; + this.netmask = netmask; + this.count = count; + } + + public boolean isRevoke() { + return revoke; + } + + public void setRevoke(boolean revoke) { + this.revoke = revoke; + } + + public String getIpAddress() { + return IpAddress; + } + + public void setIpAddress(String ipAddress) { + IpAddress = ipAddress; + } + + public String getNetmask() { + return netmask; + } + + public void setNetmask(String netmask) { + this.netmask = netmask; + } + + public long getCount() { + return count; + } + + public void setCount(long count) { + this.count = count; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/IpAliases.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/IpAliases.java new file mode 100644 index 0000000000..b7dde70de0 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/IpAliases.java @@ -0,0 +1,44 @@ +// +// 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.agent.resource.virtualnetwork.model; + +import java.util.List; + +public class IpAliases extends ConfigBase { + List aliases; + + public IpAliases() { + super(ConfigBase.IP_ALIAS_CONFIG); + } + + public IpAliases(List aliases) { + super(ConfigBase.IP_ALIAS_CONFIG); + this.aliases = aliases; + } + + public List getAliases() { + return aliases; + } + + public void setAliases(List aliases) { + this.aliases = aliases; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/IpAssociation.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/IpAssociation.java new file mode 100644 index 0000000000..7fac1cae29 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/IpAssociation.java @@ -0,0 +1,42 @@ +// +// 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.agent.resource.virtualnetwork.model; + +public class IpAssociation extends ConfigBase { + private IpAddress[] ipAddress; + + public IpAssociation() { + super(IP_ASSOCIATION); + } + + public IpAssociation(IpAddress[] ipAddress) { + super(IP_ASSOCIATION); + this.ipAddress = ipAddress; + } + + public IpAddress[] getIpAddress() { + return ipAddress; + } + + public void setIpAddress(IpAddress[] ipAddress) { + this.ipAddress = ipAddress; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/LoadBalancerRule.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/LoadBalancerRule.java new file mode 100644 index 0000000000..e3b6e45e14 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/LoadBalancerRule.java @@ -0,0 +1,104 @@ +// +// 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.agent.resource.virtualnetwork.model; + + +public class LoadBalancerRule { + + private String[] configuration; + private String tmpCfgFilePath; + private String tmpCfgFileName; + + private String[] addRules; + private String[] removeRules; + private String[] statRules; + + private String routerIp; + + public LoadBalancerRule() { + // Empty constructor for (de)serialization + } + + public LoadBalancerRule(final String[] configuration, final String tmpCfgFilePath, final String tmpCfgFileName, final String[] addRules, final String[] removeRules, final String[] statRules, final String routerIp) { + this.configuration = configuration; + this.tmpCfgFilePath = tmpCfgFilePath; + this.tmpCfgFileName = tmpCfgFileName; + this.addRules = addRules; + this.removeRules = removeRules; + this.statRules = statRules; + this.routerIp = routerIp; + } + + public String[] getConfiguration() { + return configuration; + } + + public void setConfiguration(final String[] configuration) { + this.configuration = configuration; + } + + public String getTmpCfgFilePath() { + return tmpCfgFilePath; + } + + public void setTmpCfgFilePath(final String tmpCfgFilePath) { + this.tmpCfgFilePath = tmpCfgFilePath; + } + + public String getTmpCfgFileName() { + return tmpCfgFileName; + } + + public void setTmpCfgFileName(final String tmpCfgFileName) { + this.tmpCfgFileName = tmpCfgFileName; + } + + public String[] getAddRules() { + return addRules; + } + + public void setAddRules(final String[] addRules) { + this.addRules = addRules; + } + + public String[] getRemoveRules() { + return removeRules; + } + + public void setRemoveRules(final String[] removeRules) { + this.removeRules = removeRules; + } + + public String[] getStatRules() { + return statRules; + } + + public void setStatRules(final String[] statRules) { + this.statRules = statRules; + } + + public String getRouterIp() { + return routerIp; + } + + public void setRouterIp(final String routerIp) { + this.routerIp = routerIp; + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/LoadBalancerRules.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/LoadBalancerRules.java new file mode 100644 index 0000000000..ad8158594f --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/LoadBalancerRules.java @@ -0,0 +1,43 @@ +// +// 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.agent.resource.virtualnetwork.model; + +import java.util.List; + +public class LoadBalancerRules extends ConfigBase { + List rules; + + public LoadBalancerRules() { + super(ConfigBase.LOAD_BALANCER); + } + + public LoadBalancerRules(final List rules) { + super(ConfigBase.LOAD_BALANCER); + this.rules = rules; + } + + public List getRules() { + return rules; + } + + public void setRules(final List rules) { + this.rules = rules; + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/MonitorService.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/MonitorService.java new file mode 100644 index 0000000000..fdf9e473f3 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/MonitorService.java @@ -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. +// + +package com.cloud.agent.resource.virtualnetwork.model; + +public class MonitorService extends ConfigBase { + public String config, disableMonitoring; + + public MonitorService() { + super(ConfigBase.MONITORSERVICE); + } + + public MonitorService(String config, String disableMonitoring) { + super(ConfigBase.MONITORSERVICE); + this.config = config; + this.disableMonitoring = disableMonitoring; + } + + public String getConfig() { + return config; + } + + public void setConfig(String config) { + this.config = config; + } + + public String getDisableMonitoring() { + return disableMonitoring; + } + + public void setDisableMonitoring(String disableMonitoring) { + this.disableMonitoring = disableMonitoring; + } + + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/NetworkACL.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/NetworkACL.java new file mode 100644 index 0000000000..40a7d27a77 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/NetworkACL.java @@ -0,0 +1,102 @@ +// +// 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.agent.resource.virtualnetwork.model; + +public class NetworkACL extends ConfigBase { + private String device; + private String macAddress; + private boolean privateGatewayAcl; + private String nicIp; + private String nicNetmask; + private AclRule[] ingressRules; + private AclRule[] egressRules; + + public NetworkACL() { + super(ConfigBase.NETWORK_ACL); + } + + public NetworkACL(String device, String macAddress, boolean privateGatewayAcl, String nicIp, String nicNetmask, AclRule[] ingressRules, AclRule[] egressRules) { + super(ConfigBase.NETWORK_ACL); + this.device = device; + this.macAddress = macAddress; + this.privateGatewayAcl = privateGatewayAcl; + this.nicIp = nicIp; + this.nicNetmask = nicNetmask; + this.ingressRules = ingressRules; + this.egressRules = egressRules; + } + + public String getDevice() { + return device; + } + + public void setDevice(String device) { + this.device = device; + } + + public String getMacAddress() { + return macAddress; + } + + public void setMacAddress(String macAddress) { + this.macAddress = macAddress; + } + + public boolean isPrivateGatewayAcl() { + return privateGatewayAcl; + } + + public void setPrivateGatewayAcl(boolean privateGatewayAcl) { + this.privateGatewayAcl = privateGatewayAcl; + } + + public String getNicIp() { + return nicIp; + } + + public void setNicIp(String nicIp) { + this.nicIp = nicIp; + } + + public String getNicNetmask() { + return nicNetmask; + } + + public void setNicNetmask(String nicNetmask) { + this.nicNetmask = nicNetmask; + } + + public AclRule[] getIngressRules() { + return ingressRules; + } + + public void setIngressRules(AclRule[] ingressRules) { + this.ingressRules = ingressRules; + } + + public AclRule[] getEgressRules() { + return egressRules; + } + + public void setEgressRules(AclRule[] egressRules) { + this.egressRules = egressRules; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ProtocolAclRule.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ProtocolAclRule.java new file mode 100644 index 0000000000..02edc81ba4 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ProtocolAclRule.java @@ -0,0 +1,43 @@ +// +// 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.agent.resource.virtualnetwork.model; + +public class ProtocolAclRule extends AclRule { + private final String type = "protocol"; + private int protocol; + + public ProtocolAclRule() { + // Empty constructor for (de)serialization + } + + public ProtocolAclRule(String cidr, boolean allowed, int protocol) { + super(cidr, allowed); + this.protocol = protocol; + } + + public int getProtocol() { + return protocol; + } + + public void setProtocol(int protocol) { + this.protocol = protocol; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/RemoteAccessVpn.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/RemoteAccessVpn.java new file mode 100644 index 0000000000..5b5c05bf7f --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/RemoteAccessVpn.java @@ -0,0 +1,98 @@ +// +// 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.agent.resource.virtualnetwork.model; + +public class RemoteAccessVpn extends ConfigBase { + + public boolean create; + public String ipRange, presharedKey, vpnServerIp, localIp, localCidr, publicInterface; + + public RemoteAccessVpn() { + super(ConfigBase.REMOTEACCESSVPN); + } + + public RemoteAccessVpn(boolean create, String ipRange, String presharedKey, String vpnServerIp, String localIp, String localCidr, String publicInterface) { + super(ConfigBase.REMOTEACCESSVPN); + this.create = create; + this.ipRange = ipRange; + this.presharedKey = presharedKey; + this.vpnServerIp = vpnServerIp; + this.localIp = localIp; + this.localCidr = localCidr; + this.publicInterface = publicInterface; + } + + public boolean isCreate() { + return create; + } + + public void setCreate(boolean create) { + this.create = create; + } + + public String getIpRange() { + return ipRange; + } + + public void setIpRange(String ipRange) { + this.ipRange = ipRange; + } + + public String getPresharedKey() { + return presharedKey; + } + + public void setPresharedKey(String presharedKey) { + this.presharedKey = presharedKey; + } + + public String getVpnServerIp() { + return vpnServerIp; + } + + public void setVpnServerIp(String vpnServerIp) { + this.vpnServerIp = vpnServerIp; + } + + public String getLocalIp() { + return localIp; + } + + public void setLocalIp(String localIp) { + this.localIp = localIp; + } + + public String getLocalCidr() { + return localCidr; + } + + public void setLocalCidr(String localCidr) { + this.localCidr = localCidr; + } + + public String getPublicInterface() { + return publicInterface; + } + + public void setPublicInterface(String publicInterface) { + this.publicInterface = publicInterface; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/Site2SiteVpn.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/Site2SiteVpn.java new file mode 100644 index 0000000000..232e99f099 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/Site2SiteVpn.java @@ -0,0 +1,164 @@ +// +// 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.agent.resource.virtualnetwork.model; + +public class Site2SiteVpn extends ConfigBase { + + private String localPublicIp, localGuestCidr, localPublicGateway, peerGatewayIp, peerGuestCidrList, espPolicy, ikePolicy, ipsecPsk; + private Long ikeLifetime, espLifetime; + private boolean create, dpd, passive, encap; + + public Site2SiteVpn() { + super(ConfigBase.SITE2SITEVPN); + } + + public Site2SiteVpn(String localPublicIp, String localGuestCidr, String localPublicGateway, String peerGatewayIp, String peerGuestCidrList, String espPolicy, + String ikePolicy, + String ipsecPsk, Long ikeLifetime, Long espLifetime, boolean create, Boolean dpd, boolean passive, boolean encap) { + super(ConfigBase.SITE2SITEVPN); + this.localPublicIp = localPublicIp; + this.localGuestCidr = localGuestCidr; + this.localPublicGateway = localPublicGateway; + this.peerGatewayIp = peerGatewayIp; + this.peerGuestCidrList = peerGuestCidrList; + this.espPolicy = espPolicy; + this.ikePolicy = ikePolicy; + this.ipsecPsk = ipsecPsk; + this.ikeLifetime = ikeLifetime; + this.espLifetime = espLifetime; + this.create = create; + this.dpd = dpd; + this.passive = passive; + this.encap = encap; + } + + public String getLocalPublicIp() { + return localPublicIp; + } + + public void setLocalPublicIp(String localPublicIp) { + this.localPublicIp = localPublicIp; + } + + public String getLocalGuestCidr() { + return localGuestCidr; + } + + public void setLocalGuestCidr(String localGuestCidr) { + this.localGuestCidr = localGuestCidr; + } + + public String getLocalPublicGateway() { + return localPublicGateway; + } + + public void setLocalPublicGateway(String localPublicGateway) { + this.localPublicGateway = localPublicGateway; + } + + public String getPeerGatewayIp() { + return peerGatewayIp; + } + + public void setPeerGatewayIp(String peerGatewayIp) { + this.peerGatewayIp = peerGatewayIp; + } + + public String getPeerGuestCidrList() { + return peerGuestCidrList; + } + + public void setPeerGuestCidrList(String peerGuestCidrList) { + this.peerGuestCidrList = peerGuestCidrList; + } + + public String getEspPolicy() { + return espPolicy; + } + + public void setEspPolicy(String espPolicy) { + this.espPolicy = espPolicy; + } + + public String getIkePolicy() { + return ikePolicy; + } + + public void setIkePolicy(String ikePolicy) { + this.ikePolicy = ikePolicy; + } + + public String getIpsecPsk() { + return ipsecPsk; + } + + public void setIpsecPsk(String ipsecPsk) { + this.ipsecPsk = ipsecPsk; + } + + public Long getIkeLifetime() { + return ikeLifetime; + } + + public void setIkeLifetime(Long ikeLifetime) { + this.ikeLifetime = ikeLifetime; + } + + public Long getEspLifetime() { + return espLifetime; + } + + public void setEspLifetime(Long espLifetime) { + this.espLifetime = espLifetime; + } + + public boolean isCreate() { + return create; + } + + public void setCreate(boolean create) { + this.create = create; + } + + public boolean isDpd() { + return dpd; + } + + public void setDpd(boolean dpd) { + this.dpd = dpd; + } + + public boolean isPassive() { + return passive; + } + + public void setPassive(boolean passive) { + this.passive = passive; + } + + public boolean getEncap() { + return encap; + } + + public void setEncap(boolean encap) { + this.encap = encap; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/StaticNatRule.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/StaticNatRule.java new file mode 100644 index 0000000000..a375a913b2 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/StaticNatRule.java @@ -0,0 +1,82 @@ +// +// 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.agent.resource.virtualnetwork.model; + +public class StaticNatRule { + private boolean revoke; + private String protocol; + private String sourceIpAddress; + private String sourcePortRange; + private String destinationIpAddress; + + public StaticNatRule() { + // Empty constructor for (de)serialization + } + + public StaticNatRule(boolean revoke, String protocol, String sourceIpAddress, String sourcePortRange, String destinationIpAddress) { + super(); + this.revoke = revoke; + this.protocol = protocol; + this.sourceIpAddress = sourceIpAddress; + this.sourcePortRange = sourcePortRange; + this.destinationIpAddress = destinationIpAddress; + } + + public boolean isRevoke() { + return revoke; + } + + public void setRevoke(boolean revoke) { + this.revoke = revoke; + } + + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public String getSourceIpAddress() { + return sourceIpAddress; + } + + public void setSourceIpAddress(String sourceIpAddress) { + this.sourceIpAddress = sourceIpAddress; + } + + public String getSourcePortRange() { + return sourcePortRange; + } + + public void setSourcePortRange(String sourcePortRange) { + this.sourcePortRange = sourcePortRange; + } + + public String getDestinationIpAddress() { + return destinationIpAddress; + } + + public void setDestinationIpAddress(String destinationIpAddress) { + this.destinationIpAddress = destinationIpAddress; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/StaticNatRules.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/StaticNatRules.java new file mode 100644 index 0000000000..606adddb90 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/StaticNatRules.java @@ -0,0 +1,44 @@ +// +// 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.agent.resource.virtualnetwork.model; + +import java.util.List; + +public class StaticNatRules extends ConfigBase { + private List rules; + + public StaticNatRules() { + super(ConfigBase.STATICNAT_RULES); + } + + public StaticNatRules(List rules) { + super(ConfigBase.STATICNAT_RULES); + this.rules = rules; + } + + public List getRules() { + return rules; + } + + public void setRules(List rules) { + this.rules = rules; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/StaticRoute.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/StaticRoute.java new file mode 100644 index 0000000000..45dd7d1d79 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/StaticRoute.java @@ -0,0 +1,62 @@ +// +// 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.agent.resource.virtualnetwork.model; + +public class StaticRoute { + private boolean revoke; + private String ipAddress; + private String cidr; + + public StaticRoute() { + // Empty constructor for (de)serialization + } + + public StaticRoute(boolean revoke, String ipAddress, String cidr) { + super(); + this.revoke = revoke; + this.ipAddress = ipAddress; + this.cidr = cidr; + } + + public boolean isRevoke() { + return revoke; + } + + public void setRevoke(boolean revoke) { + this.revoke = revoke; + } + + public String getIpAddress() { + return ipAddress; + } + + public void setIpAddress(String ipAddress) { + this.ipAddress = ipAddress; + } + + public String getCidr() { + return cidr; + } + + public void setCidr(String cidr) { + this.cidr = cidr; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/StaticRoutes.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/StaticRoutes.java new file mode 100644 index 0000000000..e05e8d1724 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/StaticRoutes.java @@ -0,0 +1,44 @@ +// +// 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.agent.resource.virtualnetwork.model; + +import java.util.List; + +public class StaticRoutes extends ConfigBase { + private List routes; + + public StaticRoutes() { + super(ConfigBase.STATIC_ROUTES); + } + + public StaticRoutes(List routes) { + super(ConfigBase.STATIC_ROUTES); + this.routes = routes; + } + + public List getRoutes() { + return routes; + } + + public void setRoutes(List routes) { + this.routes = routes; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/TcpAclRule.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/TcpAclRule.java new file mode 100644 index 0000000000..afcef96509 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/TcpAclRule.java @@ -0,0 +1,53 @@ +// +// 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.agent.resource.virtualnetwork.model; + +public class TcpAclRule extends AclRule { + private final String type = "tcp"; + private int firstPort; + private int lastPort; + + public TcpAclRule() { + // Empty contructor for (de)serialization + } + + public TcpAclRule(String cidr, boolean allowed, int firstPort, int lastPort) { + super(cidr, allowed); + this.firstPort = firstPort; + this.lastPort = lastPort; + } + + public int getFirstPort() { + return firstPort; + } + + public void setFirstPort(int firstPort) { + this.firstPort = firstPort; + } + + public int getLastPort() { + return lastPort; + } + + public void setLastPort(int lastPort) { + this.lastPort = lastPort; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/UdpAclRule.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/UdpAclRule.java new file mode 100644 index 0000000000..1756428369 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/UdpAclRule.java @@ -0,0 +1,53 @@ +// +// 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.agent.resource.virtualnetwork.model; + +public class UdpAclRule extends AclRule { + private final String type = "udp"; + private int firstPort; + private int lastPort; + + public UdpAclRule() { + // Empty contructor for (de)serialization + } + + public UdpAclRule(String cidr, boolean allowed, int firstPort, int lastPort) { + super(cidr, allowed); + this.firstPort = firstPort; + this.lastPort = lastPort; + } + + public int getFirstPort() { + return firstPort; + } + + public void setFirstPort(int firstPort) { + this.firstPort = firstPort; + } + + public int getLastPort() { + return lastPort; + } + + public void setLastPort(int lastPort) { + this.lastPort = lastPort; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VmData.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VmData.java new file mode 100644 index 0000000000..50ee885454 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VmData.java @@ -0,0 +1,54 @@ +// +// 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.agent.resource.virtualnetwork.model; + +import java.util.List; + +public class VmData extends ConfigBase { + private String vmIpAddress; + private List vmMetadata; + + public VmData() { + super(ConfigBase.VM_METADATA); + } + + public VmData(String vmIpAddress, List vmMetadata) { + super(ConfigBase.VM_METADATA); + this.vmIpAddress = vmIpAddress; + this.vmMetadata = vmMetadata; + } + + public String getVmIpAddress() { + return vmIpAddress; + } + + public void setVmIpAddress(String vmIpAddress) { + this.vmIpAddress = vmIpAddress; + } + + public List getVmMetadata() { + return vmMetadata; + } + + public void setVmMetadata(List vmMetadata) { + this.vmMetadata = vmMetadata; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VmDhcpConfig.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VmDhcpConfig.java new file mode 100644 index 0000000000..28e6b9bef2 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VmDhcpConfig.java @@ -0,0 +1,123 @@ +// +// 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.agent.resource.virtualnetwork.model; + +public class VmDhcpConfig extends ConfigBase { + private String hostName; + private String macAddress; + private String ipv4Adress; + private String ipv6Address; + private String ipv6Duid; + private String dnsAdresses; + private String defaultGateway; + private String staticRoutes; + private boolean defaultEntry; + + public VmDhcpConfig() { + super(VM_DHCP); + } + + public VmDhcpConfig(String hostName, String macAddress, String ipv4Adress, String ipv6Address, String ipv6Duid, String dnsAdresses, String defaultGateway, + String staticRoutes, boolean defaultEntry) { + super(VM_DHCP); + this.hostName = hostName; + this.macAddress = macAddress; + this.ipv4Adress = ipv4Adress; + this.ipv6Address = ipv6Address; + this.ipv6Duid = ipv6Duid; + this.dnsAdresses = dnsAdresses; + this.defaultGateway = defaultGateway; + this.staticRoutes = staticRoutes; + this.defaultEntry = defaultEntry; + } + + public String getHostName() { + return hostName; + } + + public void setHostName(String hostName) { + this.hostName = hostName; + } + + public String getMacAddress() { + return macAddress; + } + + public void setMacAddress(String macAddress) { + this.macAddress = macAddress; + } + + public String getIpv4Adress() { + return ipv4Adress; + } + + public void setIpv4Adress(String ipv4Adress) { + this.ipv4Adress = ipv4Adress; + } + + public String getIpv6Address() { + return ipv6Address; + } + + public void setIpv6Address(String ipv6Address) { + this.ipv6Address = ipv6Address; + } + + public String getIpv6Duid() { + return ipv6Duid; + } + + public void setIpv6Duid(String ipv6Duid) { + this.ipv6Duid = ipv6Duid; + } + + public String getDnsAdresses() { + return dnsAdresses; + } + + public void setDnsAdresses(String dnsAdresses) { + this.dnsAdresses = dnsAdresses; + } + + public String getDefaultGateway() { + return defaultGateway; + } + + public void setDefaultGateway(String defaultGateway) { + this.defaultGateway = defaultGateway; + } + + public String getStaticRoutes() { + return staticRoutes; + } + + public void setStaticRoutes(String staticRoutes) { + this.staticRoutes = staticRoutes; + } + + public boolean isDefaultEntry() { + return defaultEntry; + } + + public void setDefaultEntry(boolean defaultEntry) { + this.defaultEntry = defaultEntry; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VmPassword.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VmPassword.java new file mode 100644 index 0000000000..042fd4e6cf --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VmPassword.java @@ -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. +// + +package com.cloud.agent.resource.virtualnetwork.model; + +public class VmPassword extends ConfigBase { + private String ipAddress; + private String password; + + public VmPassword() { + super(ConfigBase.VM_PASSWORD); + } + + public VmPassword(String ipAddress, String password) { + super(ConfigBase.VM_PASSWORD); + this.ipAddress = ipAddress; + this.password = password; + } + + public String getIpAddress() { + return ipAddress; + } + + public void setIpAddress(String ipAddress) { + this.ipAddress = ipAddress; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VpnUser.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VpnUser.java new file mode 100644 index 0000000000..be50e7b18d --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VpnUser.java @@ -0,0 +1,62 @@ +// +// 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.agent.resource.virtualnetwork.model; + +public class VpnUser { + private String user; + private String password; + private boolean add; + + public VpnUser() { + // Empty constructor for serialization + } + + public VpnUser(String user, String password, boolean add) { + super(); + this.user = user; + this.password = password; + this.add = add; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public boolean isAdd() { + return add; + } + + public void setAdd(boolean add) { + this.add = add; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VpnUserList.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VpnUserList.java new file mode 100644 index 0000000000..115fcc9bd1 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VpnUserList.java @@ -0,0 +1,44 @@ +// +// 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.agent.resource.virtualnetwork.model; + +import java.util.List; + +public class VpnUserList extends ConfigBase { + private List vpnUsers; + + public VpnUserList() { + super(ConfigBase.VPN_USER_LIST); + } + + public VpnUserList(List vpnUsers) { + super(ConfigBase.VPN_USER_LIST); + this.vpnUsers = vpnUsers; + } + + public List getVpnUsers() { + return vpnUsers; + } + + public void setVpnUsers(List vpnUsers) { + this.vpnUsers = vpnUsers; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/transport/ArrayTypeAdaptor.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/transport/ArrayTypeAdaptor.java new file mode 100644 index 0000000000..30f995f128 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/transport/ArrayTypeAdaptor.java @@ -0,0 +1,86 @@ +// +// 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.agent.transport; + +import java.lang.reflect.Array; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Map; + +import com.cloud.utils.exception.CloudRuntimeException; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +public class ArrayTypeAdaptor implements JsonDeserializer, JsonSerializer { + + protected Gson _gson = null; + + public ArrayTypeAdaptor() { + } + + public void initGson(Gson gson) { + _gson = gson; + } + + @Override + public JsonElement serialize(T[] src, Type typeOfSrc, JsonSerializationContext context) { + JsonArray array = new JsonArray(); + for (T cmd : src) { + JsonObject obj = new JsonObject(); + obj.add(cmd.getClass().getName(), _gson.toJsonTree(cmd)); + array.add(obj); + } + + return array; + } + + @Override + @SuppressWarnings("unchecked") + public T[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonArray array = json.getAsJsonArray(); + Iterator it = array.iterator(); + ArrayList cmds = new ArrayList(); + while (it.hasNext()) { + JsonObject element = (JsonObject)it.next(); + Map.Entry entry = element.entrySet().iterator().next(); + + String name = entry.getKey(); + Class clazz; + try { + clazz = Class.forName(name); + } catch (ClassNotFoundException e) { + throw new CloudRuntimeException("can't find " + name); + } + T cmd = (T)_gson.fromJson(entry.getValue(), clazz); + cmds.add(cmd); + } + Class type = ((Class)typeOfT).getComponentType(); + T[] ts = (T[])Array.newInstance(type, cmds.size()); + return cmds.toArray(ts); + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/transport/InterfaceTypeAdaptor.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/transport/InterfaceTypeAdaptor.java new file mode 100644 index 0000000000..dc5f9a109c --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/transport/InterfaceTypeAdaptor.java @@ -0,0 +1,67 @@ +// +// 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.agent.transport; + +import java.lang.reflect.Type; +import java.util.Map; + +import com.cloud.utils.exception.CloudRuntimeException; +import com.google.gson.Gson; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +public class InterfaceTypeAdaptor implements JsonDeserializer, JsonSerializer { + + protected Gson _gson = null; + + public InterfaceTypeAdaptor() { + } + + public void initGson(Gson gson) { + _gson = gson; + } + + @Override + public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject obj = new JsonObject(); + obj.add(src.getClass().getName(), _gson.toJsonTree(src)); + return obj; + } + + @Override + @SuppressWarnings("unchecked") + public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonObject element = (JsonObject)json; + Map.Entry entry = element.entrySet().iterator().next(); + String name = entry.getKey(); + Class clazz; + try { + clazz = Class.forName(name); + } catch (ClassNotFoundException e) { + throw new CloudRuntimeException("can't find " + name); + } + return (T)_gson.fromJson(entry.getValue(), clazz); + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/transport/LoggingExclusionStrategy.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/transport/LoggingExclusionStrategy.java new file mode 100644 index 0000000000..4d8afbd167 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/transport/LoggingExclusionStrategy.java @@ -0,0 +1,58 @@ +// +// 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.agent.transport; + +import com.cloud.agent.api.Command; +import com.cloud.agent.api.LogLevel; +import com.cloud.agent.api.LogLevel.Log4jLevel; +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; + +import org.apache.log4j.Logger; + +public class LoggingExclusionStrategy implements ExclusionStrategy { + Logger _logger = null; + + @Override + public boolean shouldSkipClass(Class clazz) { + if (clazz.isArray() || !Command.class.isAssignableFrom(clazz)) { + return false; + } + Log4jLevel log4jLevel = null; + LogLevel level = clazz.getAnnotation(LogLevel.class); + if (level == null) { + log4jLevel = LogLevel.Log4jLevel.Debug; + } else { + log4jLevel = level.value(); + } + + return !log4jLevel.enabled(_logger); + } + + @Override + public boolean shouldSkipField(FieldAttributes field) { + LogLevel level = field.getAnnotation(LogLevel.class); + return level != null && !level.value().enabled(_logger); + } + + public LoggingExclusionStrategy(Logger logger) { + _logger = logger; + } +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/transport/Request.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/transport/Request.java new file mode 100644 index 0000000000..873025936c --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/transport/Request.java @@ -0,0 +1,658 @@ +// +// 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.agent.transport; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.StringReader; +import java.lang.reflect.Type; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.SecStorageFirewallCfgCommand.PortConfig; +import com.cloud.exception.UnsupportedVersionException; +import com.cloud.serializer.GsonHelper; +import com.cloud.utils.NumbersUtil; +import com.cloud.utils.Pair; +import com.cloud.utils.StringUtils; +import com.cloud.utils.exception.CloudRuntimeException; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.google.gson.stream.JsonReader; + +import org.apache.log4j.Level; +import org.apache.log4j.Logger; + +/** + * Request is a simple wrapper around command and answer to add sequencing, + * versioning, and flags. Note that the version here represents the changes + * in the over the wire protocol. For example, if we decide to not use Gson. + * It does not version the changes in the actual commands. That's expected + * to be done by adding new classes to the command and answer list. + * + * A request looks as follows: + * 1. Version - 1 byte; + * 2. Flags - 3 bytes; + * 3. Sequence - 8 bytes; + * 4. Length - 4 bytes; + * 5. ManagementServerId - 8 bytes; + * 6. AgentId - 8 bytes; + * 7. Data Package. + * + */ +public class Request { + private static final Logger s_logger = Logger.getLogger(Request.class); + + protected static final Gson s_gson = GsonHelper.getGson(); + protected static final Gson s_gogger = GsonHelper.getGsonLogger(); + protected static final Logger s_gsonLogger = GsonHelper.getLogger(); + + public enum Version { + v1, // using gson to marshall + v2, // now using gson as marshalled. + v3; // Adding routing information into the Request data structure. + + public static Version get(final byte ver) throws UnsupportedVersionException { + for (final Version version : Version.values()) { + if (ver == version.ordinal()) { + return version; + } + } + throw new UnsupportedVersionException("Can't lookup version: " + ver, UnsupportedVersionException.UnknownVersion); + } + }; + + protected static final short FLAG_RESPONSE = 0x0; + protected static final short FLAG_REQUEST = 0x1; + protected static final short FLAG_STOP_ON_ERROR = 0x2; + protected static final short FLAG_IN_SEQUENCE = 0x4; + protected static final short FLAG_FROM_SERVER = 0x20; + protected static final short FLAG_CONTROL = 0x40; + protected static final short FLAG_COMPRESSED = 0x80; + + protected Version _ver; + protected long _session; + protected long _seq; + protected short _flags; + protected long _mgmtId; + protected long _via; + protected long _agentId; + protected Command[] _cmds; + protected String _content; + protected String _agentName; + + protected Request() { + } + + protected Request(Version ver, long seq, long agentId, long mgmtId, long via, short flags, final Command[] cmds) { + _ver = ver; + _cmds = cmds; + _flags = flags; + _seq = seq; + _via = via; + _agentId = agentId; + _mgmtId = mgmtId; + setInSequence(cmds); + } + + protected Request(Version ver, long seq, long agentId, long mgmtId, short flags, final Command[] cmds) { + this(ver, seq, agentId, mgmtId, agentId, flags, cmds); + } + + protected Request(Version ver, long seq, long agentId, long mgmtId, long via, short flags, final String content) { + this(ver, seq, agentId, mgmtId, via, flags, (Command[])null); + _content = content; + } + + public Request(long agentId, long mgmtId, Command command, boolean fromServer) { + this(agentId, mgmtId, new Command[] {command}, true, fromServer); + } + + public Request(long agentId, long mgmtId, Command[] cmds, boolean stopOnError, boolean fromServer) { + this(Version.v1, -1l, agentId, mgmtId, (short)0, cmds); + setStopOnError(stopOnError); + setFromServer(fromServer); + } + + public Request(long agentId, String agentName, long mgmtId, Command[] cmds, boolean stopOnError, boolean fromServer) { + this(agentId, mgmtId, cmds, stopOnError, fromServer); + setAgentName(agentName); + } + + public void setSequence(long seq) { + _seq = seq; + } + + protected void setInSequence(Command[] cmds) { + if (cmds == null) { + return; + } + for (Command cmd : cmds) { + if (cmd.executeInSequence()) { + setInSequence(true); + break; + } + } + } + + protected Request(final Request that, final Command[] cmds) { + _ver = that._ver; + _seq = that._seq; + setInSequence(that.executeInSequence()); + setStopOnError(that.stopOnError()); + _cmds = cmds; + _mgmtId = that._mgmtId; + _via = that._via; + _agentId = that._agentId; + _agentName = that._agentName; + setFromServer(!that.isFromServer()); + } + + private final void setStopOnError(boolean stopOnError) { + _flags |= (stopOnError ? FLAG_STOP_ON_ERROR : 0); + } + + private final void setAgentName(String agentName) { + _agentName = agentName; + } + + private final void setInSequence(boolean inSequence) { + _flags |= (inSequence ? FLAG_IN_SEQUENCE : 0); + } + + public boolean isControl() { + return (_flags & FLAG_CONTROL) > 0; + } + + public void setControl(boolean control) { + _flags |= (control ? FLAG_CONTROL : 0); + } + + private final void setFromServer(boolean fromServer) { + _flags |= (fromServer ? FLAG_FROM_SERVER : 0); + } + + public long getManagementServerId() { + return _mgmtId; + } + + public boolean isFromServer() { + return (_flags & FLAG_FROM_SERVER) > 0; + } + + public Version getVersion() { + return _ver; + } + + public void setAgentId(long agentId) { + _agentId = agentId; + } + + public void setVia(long viaId) { + _via = viaId; + } + + public boolean executeInSequence() { + return (_flags & FLAG_IN_SEQUENCE) > 0; + } + + public long getSequence() { + return _seq; + } + + public boolean stopOnError() { + return (_flags & FLAG_STOP_ON_ERROR) > 0; + } + + public Command getCommand() { + getCommands(); + return _cmds[0]; + } + + public Command[] getCommands() { + if (_cmds == null) { + try { + StringReader reader = new StringReader(_content); + JsonReader jsonReader = new JsonReader(reader); + jsonReader.setLenient(true); + _cmds = s_gson.fromJson(jsonReader, (Type)Command[].class); + } catch (RuntimeException e) { + s_logger.error("Caught problem with " + _content, e); + throw e; + } + } + return _cmds; + } + + protected String getType() { + return "Cmd "; + } + + protected ByteBuffer serializeHeader(final int contentSize) { + final ByteBuffer buffer = ByteBuffer.allocate(40); + buffer.put(getVersionInByte()); + buffer.put((byte)0); + buffer.putShort(getFlags()); + buffer.putLong(_seq); + // The size here is uncompressed size, if the data is compressed. + buffer.putInt(contentSize); + buffer.putLong(_mgmtId); + buffer.putLong(_agentId); + buffer.putLong(_via); + buffer.flip(); + + return buffer; + } + + public static ByteBuffer doDecompress(ByteBuffer buffer, int length) { + byte[] byteArrayIn = new byte[1024]; + ByteArrayInputStream byteIn; + if (buffer.hasArray()) { + byteIn = new ByteArrayInputStream(buffer.array(), buffer.position() + buffer.arrayOffset(), buffer.remaining()); + } else { + byte[] array = new byte[buffer.limit() - buffer.position()]; + buffer.get(array); + byteIn = new ByteArrayInputStream(array); + } + ByteBuffer retBuff = ByteBuffer.allocate(length); + int len = 0; + try { + GZIPInputStream in = new GZIPInputStream(byteIn); + while ((len = in.read(byteArrayIn)) > 0) { + retBuff.put(byteArrayIn, 0, len); + } + in.close(); + } catch (IOException e) { + s_logger.error("Fail to decompress the request!", e); + } + retBuff.flip(); + return retBuff; + } + + public static ByteBuffer doCompress(ByteBuffer buffer, int length) { + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(length); + byte[] array; + if (buffer.hasArray()) { + array = buffer.array(); + } else { + array = new byte[buffer.capacity()]; + buffer.get(array); + } + try { + GZIPOutputStream out = new GZIPOutputStream(byteOut, length); + out.write(array); + out.finish(); + out.close(); + } catch (IOException e) { + s_logger.error("Fail to compress the request!", e); + } + return ByteBuffer.wrap(byteOut.toByteArray()); + } + + public ByteBuffer[] toBytes() { + final ByteBuffer[] buffers = new ByteBuffer[2]; + ByteBuffer tmp; + + if (_content == null) { + _content = s_gson.toJson(_cmds, _cmds.getClass()); + } + tmp = ByteBuffer.wrap(_content.getBytes()); + int capacity = tmp.capacity(); + /* Check if we need to compress the data */ + if (capacity >= 8192) { + tmp = doCompress(tmp, capacity); + _flags |= FLAG_COMPRESSED; + } + buffers[1] = tmp; + buffers[0] = serializeHeader(capacity); + + return buffers; + } + + public byte[] getBytes() { + final ByteBuffer[] buffers = toBytes(); + final int len1 = buffers[0].remaining(); + final int len2 = buffers[1].remaining(); + final byte[] bytes = new byte[len1 + len2]; + buffers[0].get(bytes, 0, len1); + buffers[1].get(bytes, len1, len2); + return bytes; + } + + protected byte getVersionInByte() { + return (byte)_ver.ordinal(); + } + + protected short getFlags() { + return (short)(((this instanceof Response) ? FLAG_RESPONSE : FLAG_REQUEST) | _flags); + } + + public void logD(String msg) { + logD(msg, true); + } + + public void logD(String msg, boolean logContent) { + if (s_logger.isDebugEnabled()) { + String log = log(msg, logContent, Level.DEBUG); + if (log != null) { + s_logger.debug(log); + } + } + } + + public void logT(String msg, boolean logD) { + if (s_logger.isTraceEnabled()) { + String log = log(msg, true, Level.TRACE); + if (log != null) { + s_logger.trace(log); + } + } else if (logD && s_logger.isDebugEnabled()) { + String log = log(msg, false, Level.DEBUG); + if (log != null) { + s_logger.debug(log); + } + } + } + + @Override + public String toString() { + return log("", true, Level.DEBUG); + } + + protected String log(String msg, boolean logContent, Level level) { + StringBuilder content = new StringBuilder(); + if (logContent) { + if (_cmds == null) { + try { + _cmds = s_gson.fromJson(_content, this instanceof Response ? Answer[].class : Command[].class); + } catch (RuntimeException e) { + s_logger.error("Unable to convert to json: " + _content); + throw e; + } + } + try { + s_gogger.toJson(_cmds, content); + } catch (Throwable e) { + StringBuilder buff = new StringBuilder(); + for (Command cmd : _cmds) { + buff.append(cmd.getClass().getSimpleName()).append("/"); + } + s_logger.error("Gson serialization error " + buff.toString(), e); + assert false : "More gson errors on " + buff.toString(); + return ""; + } + if (content.length() <= (1 + _cmds.length * 3)) { + return null; + } + } else { + if (_cmds == null) { + _cmds = s_gson.fromJson(_content, this instanceof Response ? Answer[].class : Command[].class); + } + content.append("{ "); + for (Command cmd : _cmds) { + content.append(cmd.getClass().getSimpleName()).append(", "); + } + content.replace(content.length() - 2, content.length(), " }"); + + } + + StringBuilder buf = new StringBuilder("Seq "); + + buf.append(_agentId).append("-").append(_seq).append(": "); + + buf.append(msg); + buf.append(" { ").append(getType()); + if (_agentName != null) { + buf.append(", MgmtId: ").append(_mgmtId).append(", via: ").append(_via).append("(" + _agentName + ")"); + } else { + buf.append(", MgmtId: ").append(_mgmtId).append(", via: ").append(_via); + } + buf.append(", Ver: ").append(_ver.toString()); + buf.append(", Flags: ").append(Integer.toBinaryString(getFlags())).append(", "); + String cleanContent = content.toString(); + if(cleanContent.contains("password")) { + buf.append(cleanPassword(cleanContent)); + } else { + buf.append(content); + } + buf.append(" }"); + return buf.toString(); + } + + public static String cleanPassword(String logString) { + String cleanLogString = null; + if (logString != null) { + cleanLogString = logString; + String[] temp = logString.split(","); + int i = 0; + if (temp != null) { + while (i < temp.length) { + temp[i] = StringUtils.cleanString(temp[i]); + i++; + } + List stringList = new ArrayList(); + Collections.addAll(stringList, temp); + cleanLogString = StringUtils.join(stringList, ","); + } + } + return cleanLogString; + } + + /** + * Factory method for Request and Response. It expects the bytes to be + * correctly formed so it's possible that it throws underflow exceptions + * but you shouldn't be concerned about that since that all bytes sent in + * should already be formatted correctly. + * + * @param bytes bytes to be converted. + * @return Request or Response depending on the data. + * @throws ClassNotFoundException if the Command or Answer can not be formed. + * @throws + */ + public static Request parse(final byte[] bytes) throws ClassNotFoundException, UnsupportedVersionException { + ByteBuffer buff = ByteBuffer.wrap(bytes); + final byte ver = buff.get(); + final Version version = Version.get(ver); + if (version.ordinal() != Version.v1.ordinal() && version.ordinal() != Version.v3.ordinal()) { + throw new UnsupportedVersionException("This version is no longer supported: " + version.toString(), UnsupportedVersionException.IncompatibleVersion); + } + buff.get(); + final short flags = buff.getShort(); + final boolean isRequest = (flags & FLAG_REQUEST) > 0; + + final long seq = buff.getLong(); + // The size here is uncompressed size, if the data is compressed. + final int size = buff.getInt(); + final long mgmtId = buff.getLong(); + final long agentId = buff.getLong(); + + long via; + if (version.ordinal() == Version.v1.ordinal()) { + via = buff.getLong(); + } else { + via = agentId; + } + + if ((flags & FLAG_COMPRESSED) != 0) { + buff = doDecompress(buff, size); + } + + byte[] command = null; + int offset = 0; + if (buff.hasArray()) { + command = buff.array(); + offset = buff.arrayOffset() + buff.position(); + } else { + command = new byte[buff.remaining()]; + buff.get(command); + offset = 0; + } + + final String content = new String(command, offset, command.length - offset); + + if (isRequest) { + return new Request(version, seq, agentId, mgmtId, via, flags, content); + } else { + return new Response(Version.get(ver), seq, agentId, mgmtId, via, flags, content); + } + } + + public long getAgentId() { + return _agentId; + } + + public long getViaAgentId() { + return _via; + } + + public static boolean requiresSequentialExecution(final byte[] bytes) { + return (bytes[3] & FLAG_IN_SEQUENCE) > 0; + } + + public static Version getVersion(final byte[] bytes) throws UnsupportedVersionException { + try { + return Version.get(bytes[0]); + } catch (UnsupportedVersionException e) { + throw new CloudRuntimeException("Unsupported version: " + bytes[0]); + } + } + + public static long getManagementServerId(final byte[] bytes) { + return NumbersUtil.bytesToLong(bytes, 16); + } + + public static long getAgentId(final byte[] bytes) { + return NumbersUtil.bytesToLong(bytes, 24); + } + + public static long getViaAgentId(final byte[] bytes) { + return NumbersUtil.bytesToLong(bytes, 32); + } + + public static boolean fromServer(final byte[] bytes) { + return (bytes[3] & FLAG_FROM_SERVER) > 0; + } + + public static boolean isRequest(final byte[] bytes) { + return (bytes[3] & FLAG_REQUEST) > 0; + } + + public static long getSequence(final byte[] bytes) { + return NumbersUtil.bytesToLong(bytes, 4); + } + + public static boolean isControl(final byte[] bytes) { + return (bytes[3] & FLAG_CONTROL) > 0; + } + + public static class NwGroupsCommandTypeAdaptor implements JsonDeserializer>, JsonSerializer> { + + public NwGroupsCommandTypeAdaptor() { + } + + @Override + public JsonElement serialize(Pair src, java.lang.reflect.Type typeOfSrc, JsonSerializationContext context) { + JsonArray array = new JsonArray(); + if (src.first() != null) { + array.add(s_gson.toJsonTree(src.first())); + } else { + array.add(new JsonNull()); + } + + if (src.second() != null) { + array.add(s_gson.toJsonTree(src.second())); + } else { + array.add(new JsonNull()); + } + + return array; + } + + @Override + public Pair deserialize(JsonElement json, java.lang.reflect.Type type, JsonDeserializationContext context) throws JsonParseException { + Pair pairs = new Pair(null, null); + JsonArray array = json.getAsJsonArray(); + if (array.size() != 2) { + return pairs; + } + JsonElement element = array.get(0); + if (!element.isJsonNull()) { + pairs.first(element.getAsLong()); + } + + element = array.get(1); + if (!element.isJsonNull()) { + pairs.second(element.getAsLong()); + } + + return pairs; + } + + } + + public static class PortConfigListTypeAdaptor implements JsonDeserializer>, JsonSerializer> { + + public PortConfigListTypeAdaptor() { + } + + @Override + public JsonElement serialize(List src, Type typeOfSrc, JsonSerializationContext context) { + if (src.size() == 0) { + return new JsonNull(); + } + JsonArray array = new JsonArray(); + for (PortConfig pc : src) { + array.add(s_gson.toJsonTree(pc)); + } + + return array; + } + + @Override + public List deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + if (json.isJsonNull()) { + return new ArrayList(); + } + List pcs = new ArrayList(); + JsonArray array = json.getAsJsonArray(); + Iterator it = array.iterator(); + while (it.hasNext()) { + JsonElement element = it.next(); + pcs.add(s_gson.fromJson(element, PortConfig.class)); + } + return pcs; + } + } +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/agent/transport/Response.java b/cosmic-core/nucleo/src/main/java/com/cloud/agent/transport/Response.java new file mode 100644 index 0000000000..ef85e32a12 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/agent/transport/Response.java @@ -0,0 +1,74 @@ +// +// 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.agent.transport; + +import com.cloud.agent.api.Answer; +import com.cloud.exception.UnsupportedVersionException; + +/** + * + */ +public class Response extends Request { + protected Response() { + } + + public Response(Request request, Answer answer) { + this(request, new Answer[] {answer}); + } + + public Response(Request request, Answer answer, long mgmtId, long agentId) { + this(request, new Answer[] {answer}, mgmtId, agentId); + } + + public Response(Request request, Answer[] answers) { + super(request, answers); + } + + public Response(Request request, Answer[] answers, long mgmtId, long agentId) { + super(request, answers); + _mgmtId = mgmtId; + _via = agentId; + } + + protected Response(Version ver, long seq, long agentId, long mgmtId, long via, short flags, String ans) { + super(ver, seq, agentId, mgmtId, via, flags, ans); + } + + public Answer getAnswer() { + Answer[] answers = getAnswers(); + return answers[0]; + } + + public Answer[] getAnswers() { + if (_cmds == null) { + _cmds = s_gson.fromJson(_content, Answer[].class); + } + return (Answer[])_cmds; + } + + @Override + protected String getType() { + return "Ans: "; + } + + public static Response parse(byte[] bytes) throws ClassNotFoundException, UnsupportedVersionException { + return (Response)Request.parse(bytes); + } +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/exception/UnsupportedVersionException.java b/cosmic-core/nucleo/src/main/java/com/cloud/exception/UnsupportedVersionException.java new file mode 100644 index 0000000000..5ee8d8ec12 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/exception/UnsupportedVersionException.java @@ -0,0 +1,43 @@ +// +// 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.exception; + +import com.cloud.utils.SerialVersionUID; + +/** + */ +public class UnsupportedVersionException extends CloudException { + + private static final long serialVersionUID = SerialVersionUID.UnsupportedVersionException; + + public static final String UnknownVersion = "unknown.version"; + public static final String IncompatibleVersion = "incompatible.version"; + + String _reason; + + public UnsupportedVersionException(String message, String reason) { + super(message); + _reason = reason; + } + + public String getReason() { + return _reason; + } +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/exception/UsageServerException.java b/cosmic-core/nucleo/src/main/java/com/cloud/exception/UsageServerException.java new file mode 100644 index 0000000000..be107a6bc6 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/exception/UsageServerException.java @@ -0,0 +1,37 @@ +// +// 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.exception; + +public class UsageServerException extends CloudException { + + /** + * + */ + private static final long serialVersionUID = -8398313106067116466L; + + public UsageServerException() { + + } + + public UsageServerException(String message) { + super(message); + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/host/HostInfo.java b/cosmic-core/nucleo/src/main/java/com/cloud/host/HostInfo.java new file mode 100644 index 0000000000..edbad0701a --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/host/HostInfo.java @@ -0,0 +1,29 @@ +// +// 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.host; + +public final class HostInfo { + public static final String HYPERVISOR_VERSION = "Hypervisor.Version"; //tricky since KVM has userspace version and kernel version + public static final String HOST_OS = "Host.OS"; //Fedora, XenServer, Ubuntu, etc + public static final String HOST_OS_VERSION = "Host.OS.Version"; //12, 5.5, 9.10, etc + public static final String HOST_OS_KERNEL_VERSION = "Host.OS.Kernel.Version"; //linux-2.6.31 etc + public static final String XS620_SNAPSHOT_HOTFIX = "xs620_snapshot_hotfix"; +} + diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/info/ConsoleProxyConnectionInfo.java b/cosmic-core/nucleo/src/main/java/com/cloud/info/ConsoleProxyConnectionInfo.java new file mode 100644 index 0000000000..48819f4947 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/info/ConsoleProxyConnectionInfo.java @@ -0,0 +1,32 @@ +// +// 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.info; + +public class ConsoleProxyConnectionInfo { + public int id; + public String host; + public int port; + public String tag; + public long createTime; + public long lastUsedTime; + + public ConsoleProxyConnectionInfo() { + } +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/info/ConsoleProxyInfo.java b/cosmic-core/nucleo/src/main/java/com/cloud/info/ConsoleProxyInfo.java new file mode 100644 index 0000000000..b15abcb54f --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/info/ConsoleProxyInfo.java @@ -0,0 +1,98 @@ +// +// 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.info; + +public class ConsoleProxyInfo { + + private boolean sslEnabled; + private String proxyAddress; + private int proxyPort; + private String proxyImageUrl; + private int proxyUrlPort = 8000; + + public ConsoleProxyInfo(int proxyUrlPort) { + this.proxyUrlPort = proxyUrlPort; + } + + public ConsoleProxyInfo(boolean sslEnabled, String proxyIpAddress, int port, int proxyUrlPort, String consoleProxyUrlDomain) { + this.sslEnabled = sslEnabled; + + if (sslEnabled) { + StringBuffer sb = new StringBuffer(); + if (consoleProxyUrlDomain.startsWith("*")) { + sb.append(proxyIpAddress); + for (int i = 0; i < proxyIpAddress.length(); i++) + if (sb.charAt(i) == '.') + sb.setCharAt(i, '-'); + sb.append(consoleProxyUrlDomain.substring(1));//skip the * + } else { + //LB address + sb.append(consoleProxyUrlDomain); + } + proxyAddress = sb.toString(); + proxyPort = port; + this.proxyUrlPort = proxyUrlPort; + + proxyImageUrl = "https://" + proxyAddress; + if (proxyUrlPort != 443) + proxyImageUrl += ":" + this.proxyUrlPort; + } else { + proxyAddress = proxyIpAddress; + proxyPort = port; + this.proxyUrlPort = proxyUrlPort; + + proxyImageUrl = "http://" + proxyAddress; + if (proxyUrlPort != 80) + proxyImageUrl += ":" + proxyUrlPort; + } + } + + public String getProxyAddress() { + return proxyAddress; + } + + public void setProxyAddress(String proxyAddress) { + this.proxyAddress = proxyAddress; + } + + public int getProxyPort() { + return proxyPort; + } + + public void setProxyPort(int proxyPort) { + this.proxyPort = proxyPort; + } + + public String getProxyImageUrl() { + return proxyImageUrl; + } + + public void setProxyImageUrl(String proxyImageUrl) { + this.proxyImageUrl = proxyImageUrl; + } + + public boolean isSslEnabled() { + return sslEnabled; + } + + public void setSslEnabled(boolean sslEnabled) { + this.sslEnabled = sslEnabled; + } +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/info/ConsoleProxyStatus.java b/cosmic-core/nucleo/src/main/java/com/cloud/info/ConsoleProxyStatus.java new file mode 100644 index 0000000000..3d3dda9a50 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/info/ConsoleProxyStatus.java @@ -0,0 +1,31 @@ +// +// 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.info; + +public class ConsoleProxyStatus { + private ConsoleProxyConnectionInfo[] connections; + + public ConsoleProxyStatus() { + } + + public ConsoleProxyConnectionInfo[] getConnections() { + return connections; + } +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/info/RunningHostInfoAgregator.java b/cosmic-core/nucleo/src/main/java/com/cloud/info/RunningHostInfoAgregator.java new file mode 100644 index 0000000000..458b329ad7 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/info/RunningHostInfoAgregator.java @@ -0,0 +1,88 @@ +// +// 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.info; + +import java.util.HashMap; +import java.util.Map; + +import com.cloud.host.Host; + +public class RunningHostInfoAgregator { + + public static class ZoneHostInfo { + public static final int ROUTING_HOST_MASK = 2; + public static final int STORAGE_HOST_MASK = 4; + public static final int ALL_HOST_MASK = ROUTING_HOST_MASK | STORAGE_HOST_MASK; + + private long dcId; + + // (1 << 1) : at least one routing host is running in the zone + // (1 << 2) : at least one storage host is running in the zone + private int flags = 0; + + public long getDcId() { + return dcId; + } + + public void setDcId(long dcId) { + this.dcId = dcId; + } + + public void setFlag(int flagMask) { + flags |= flagMask; + } + + public int getFlags() { + return flags; + } + } + + private final Map zoneHostInfoMap = new HashMap(); + + public RunningHostInfoAgregator() { + } + + public void aggregate(RunningHostCountInfo countInfo) { + if (countInfo.getCount() > 0) { + ZoneHostInfo zoneInfo = getZoneHostInfo(countInfo.getDcId()); + + Host.Type type = Enum.valueOf(Host.Type.class, countInfo.getHostType()); + if (type == Host.Type.Routing) { + zoneInfo.setFlag(ZoneHostInfo.ROUTING_HOST_MASK); + } else if (type == Host.Type.Storage || type == Host.Type.SecondaryStorage) { + zoneInfo.setFlag(ZoneHostInfo.STORAGE_HOST_MASK); + } + } + } + + public Map getZoneHostInfoMap() { + return zoneHostInfoMap; + } + + private ZoneHostInfo getZoneHostInfo(long dcId) { + if (zoneHostInfoMap.containsKey(dcId)) + return zoneHostInfoMap.get(dcId); + + ZoneHostInfo info = new ZoneHostInfo(); + info.setDcId(dcId); + zoneHostInfoMap.put(dcId, info); + return info; + } +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/info/SecStorageVmLoadInfo.java b/cosmic-core/nucleo/src/main/java/com/cloud/info/SecStorageVmLoadInfo.java new file mode 100644 index 0000000000..e10d5cc047 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/info/SecStorageVmLoadInfo.java @@ -0,0 +1,51 @@ +// +// 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.info; + +public class SecStorageVmLoadInfo { + + private long id; + private String name; + private int count; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/network/HAProxyConfigurator.java b/cosmic-core/nucleo/src/main/java/com/cloud/network/HAProxyConfigurator.java new file mode 100644 index 0000000000..7971201762 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/network/HAProxyConfigurator.java @@ -0,0 +1,688 @@ +// +// 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.network; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.cloud.agent.api.routing.LoadBalancerConfigCommand; +import com.cloud.agent.api.to.LoadBalancerTO; +import com.cloud.agent.api.to.LoadBalancerTO.DestinationTO; +import com.cloud.agent.api.to.LoadBalancerTO.StickinessPolicyTO; +import com.cloud.agent.api.to.PortForwardingRuleTO; +import com.cloud.network.rules.LbStickinessMethod.StickinessMethodType; +import com.cloud.utils.Pair; +import com.cloud.utils.net.NetUtils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HAProxyConfigurator implements LoadBalancerConfigurator { + + private static final Logger s_logger = LoggerFactory.getLogger(HAProxyConfigurator.class); + private static final String blankLine = "\t "; + private static String[] globalSection = {"global", "\tlog 127.0.0.1:3914 local0 warning", "\tmaxconn 4096", "\tmaxpipes 1024", "\tchroot /var/lib/haproxy", + "\tuser haproxy", "\tgroup haproxy", "\tdaemon"}; + + private static String[] defaultsSection = {"defaults", "\tlog global", "\tmode tcp", "\toption dontlognull", "\tretries 3", "\toption redispatch", + "\toption forwardfor", "\toption forceclose", "\ttimeout connect 5000", "\ttimeout client 50000", "\ttimeout server 50000"}; + + private static String[] defaultListen = {"listen vmops 0.0.0.0:9", "\toption transparent"}; + + @Override + public String[] generateConfiguration(final List fwRules) { + // Group the rules by publicip:publicport + final Map> pools = new HashMap>(); + + for (final PortForwardingRuleTO rule : fwRules) { + final StringBuilder sb = new StringBuilder(); + final String poolName = sb.append(rule.getSrcIp().replace(".", "_")).append('-').append(rule.getSrcPortRange()[0]).toString(); + if (!rule.revoked()) { + List fwList = pools.get(poolName); + if (fwList == null) { + fwList = new ArrayList(); + pools.put(poolName, fwList); + } + fwList.add(rule); + } + } + + final List result = new ArrayList(); + + result.addAll(Arrays.asList(globalSection)); + result.add(blankLine); + result.addAll(Arrays.asList(defaultsSection)); + result.add(blankLine); + + if (pools.isEmpty()) { + // haproxy cannot handle empty listen / frontend or backend, so add + // a dummy listener + // on port 9 + result.addAll(Arrays.asList(defaultListen)); + } + result.add(blankLine); + + for (final Map.Entry> e : pools.entrySet()) { + final List poolRules = getRulesForPool(e.getKey(), e.getValue()); + result.addAll(poolRules); + } + + return result.toArray(new String[result.size()]); + } + + private List getRulesForPool(final String poolName, final List fwRules) { + final PortForwardingRuleTO firstRule = fwRules.get(0); + final String publicIP = firstRule.getSrcIp(); + final String publicPort = Integer.toString(firstRule.getSrcPortRange()[0]); + // FIXEME: String algorithm = firstRule.getAlgorithm(); + + final List result = new ArrayList(); + // add line like this: "listen 65_37_141_30-80 65.37.141.30:80" + StringBuilder sb = new StringBuilder(); + sb.append("listen ").append(poolName).append(" ").append(publicIP).append(":").append(publicPort); + result.add(sb.toString()); + sb = new StringBuilder(); + // FIXME sb.append("\t").append("balance ").append(algorithm); + result.add(sb.toString()); + if (publicPort.equals(NetUtils.HTTP_PORT) + // && global option httpclose set (or maybe not in this spot???) + ) { + sb = new StringBuilder(); + sb.append("\t").append("mode http"); + result.add(sb.toString()); + sb = new StringBuilder(); + sb.append("\t").append("option httpclose"); + result.add(sb.toString()); + } + int i = 0; + for (final PortForwardingRuleTO rule : fwRules) { + // add line like this: "server 65_37_141_30-80_3 10.1.1.4:80 check" + if (rule.revoked()) { + continue; + } + sb = new StringBuilder(); + sb.append("\t") + .append("server ") + .append(poolName) + .append("_") + .append(Integer.toString(i++)) + .append(" ") + .append(rule.getDstIp()) + .append(":") + .append(rule.getDstPortRange()[0]) + .append(" check"); + result.add(sb.toString()); + } + result.add(blankLine); + return result; + } + + /* + cookie [ rewrite | insert | prefix ] [ indirect ] [ nocache ] + [ postonly ] [ domain ]* + Enable cookie-based persistence in a backend. + May be used in sections : defaults | frontend | listen | backend + yes | no | yes | yes + Arguments : + is the name of the cookie which will be monitored, modified or + inserted in order to bring persistence. This cookie is sent to + the client via a "Set-Cookie" header in the response, and is + brought back by the client in a "Cookie" header in all requests. + Special care should be taken to choose a name which does not + conflict with any likely application cookie. Also, if the same + backends are subject to be used by the same clients (eg: + HTTP/HTTPS), care should be taken to use different cookie names + between all backends if persistence between them is not desired. + + rewrite This keyword indicates that the cookie will be provided by the + server and that haproxy will have to modify its value to set the + server's identifier in it. This mode is handy when the management + of complex combinations of "Set-cookie" and "Cache-control" + headers is left to the application. The application can then + decide whether or not it is appropriate to emit a persistence + cookie. Since all responses should be monitored, this mode only + works in HTTP close mode. Unless the application behaviour is + very complex and/or broken, it is advised not to start with this + mode for new deployments. This keyword is incompatible with + "insert" and "prefix". + + insert This keyword indicates that the persistence cookie will have to + be inserted by haproxy in the responses. If the server emits a + cookie with the same name, it will be replaced anyway. For this + reason, this mode can be used to upgrade existing configurations + running in the "rewrite" mode. The cookie will only be a session + cookie and will not be stored on the client's disk. Due to + caching effects, it is generally wise to add the "indirect" and + "nocache" or "postonly" keywords (see below). The "insert" + keyword is not compatible with "rewrite" and "prefix". + + prefix This keyword indicates that instead of relying on a dedicated + cookie for the persistence, an existing one will be completed. + This may be needed in some specific environments where the client + does not support more than one single cookie and the application + already needs it. In this case, whenever the server sets a cookie + named , it will be prefixed with the server's identifier + and a delimiter. The prefix will be removed from all client + requests so that the server still finds the cookie it emitted. + Since all requests and responses are subject to being modified, + this mode requires the HTTP close mode. The "prefix" keyword is + not compatible with "rewrite" and "insert". + + indirect When this option is specified in insert mode, cookies will only + be added when the server was not reached after a direct access, + which means that only when a server is elected after applying a + load-balancing algorithm, or after a redispatch, then the cookie + will be inserted. If the client has all the required information + to connect to the same server next time, no further cookie will + be inserted. In all cases, when the "indirect" option is used in + insert mode, the cookie is always removed from the requests + transmitted to the server. The persistence mechanism then becomes + totally transparent from the application point of view. + + nocache This option is recommended in conjunction with the insert mode + when there is a cache between the client and HAProxy, as it + ensures that a cacheable response will be tagged non-cacheable if + a cookie needs to be inserted. This is important because if all + persistence cookies are added on a cacheable home page for + instance, then all customers will then fetch the page from an + outer cache and will all share the same persistence cookie, + leading to one server receiving much more traffic than others. + See also the "insert" and "postonly" options. + + postonly This option ensures that cookie insertion will only be performed + on responses to POST requests. It is an alternative to the + "nocache" option, because POST responses are not cacheable, so + this ensures that the persistence cookie will never get cached. + Since most sites do not need any sort of persistence before the + first POST which generally is a login request, this is a very + efficient method to optimize caching without risking to find a + persistence cookie in the cache. + See also the "insert" and "nocache" options. + + domain This option allows to specify the domain at which a cookie is + inserted. It requires exactly one parameter: a valid domain + name. If the domain begins with a dot, the browser is allowed to + use it for any host ending with that name. It is also possible to + specify several domain names by invoking this option multiple + times. Some browsers might have small limits on the number of + domains, so be careful when doing that. For the record, sending + 10 domains to MSIE 6 or Firefox 2 works as expected. + + There can be only one persistence cookie per HTTP backend, and it can be + declared in a defaults section. The value of the cookie will be the value + indicated after the "cookie" keyword in a "server" statement. If no cookie + is declared for a given server, the cookie is not set. + + Examples : + cookie JSESSIONID prefix + cookie SRV insert indirect nocache + cookie SRV insert postonly indirect + + + appsession len timeout + [request-learn] [prefix] [mode ] + Define session stickiness on an existing application cookie. + May be used in sections : defaults | frontend | listen | backend + no | no | yes | yes + Arguments : + this is the name of the cookie used by the application and which + HAProxy will have to learn for each new session. + + this is the max number of characters that will be memorized and + checked in each cookie value. + + this is the time after which the cookie will be removed from + memory if unused. If no unit is specified, this time is in + milliseconds. + + request-learn + If this option is specified, then haproxy will be able to learn + the cookie found in the request in case the server does not + specify any in response. This is typically what happens with + PHPSESSID cookies, or when haproxy's session expires before + the application's session and the correct server is selected. + It is recommended to specify this option to improve reliability. + + prefix When this option is specified, haproxy will match on the cookie + prefix (or URL parameter prefix). The appsession value is the + data following this prefix. + + Example : + appsession ASPSESSIONID len 64 timeout 3h prefix + + This will match the cookie ASPSESSIONIDXXXX=XXXXX, + the appsession value will be XXXX=XXXXX. + + mode This option allows to change the URL parser mode. + 2 modes are currently supported : + - path-parameters : + The parser looks for the appsession in the path parameters + part (each parameter is separated by a semi-colon), which is + convenient for JSESSIONID for example. + This is the default mode if the option is not set. + - query-string : + In this mode, the parser will look for the appsession in the + query string. + + When an application cookie is defined in a backend, HAProxy will check when + the server sets such a cookie, and will store its value in a table, and + associate it with the server's identifier. Up to characters from + the value will be retained. On each connection, haproxy will look for this + cookie both in the "Cookie:" headers, and as a URL parameter (depending on + the mode used). If a known value is found, the client will be directed to the + server associated with this value. Otherwise, the load balancing algorithm is + applied. Cookies are automatically removed from memory when they have been + unused for a duration longer than . + + The definition of an application cookie is limited to one per backend. + Example : + appsession JSESSIONID len 52 timeout 3h + */ + private String getLbSubRuleForStickiness(final LoadBalancerTO lbTO) { + int i = 0; + + if (lbTO.getStickinessPolicies() == null) { + return null; + } + + final StringBuilder sb = new StringBuilder(); + + for (final StickinessPolicyTO stickinessPolicy : lbTO.getStickinessPolicies()) { + if (stickinessPolicy == null) { + continue; + } + final List> paramsList = stickinessPolicy.getParams(); + i++; + + /* + * cookie [ rewrite | insert | prefix ] [ indirect ] [ nocache ] + [ postonly ] [ domain ]* + + */ + if (StickinessMethodType.LBCookieBased.getName().equalsIgnoreCase(stickinessPolicy.getMethodName())) { + /* Default Values */ + String cookieName = null; // optional + String mode = "insert "; // optional + Boolean indirect = false; // optional + Boolean nocache = false; // optional + Boolean postonly = false; // optional + StringBuilder domainSb = null; // optional + + for (final Pair paramKV : paramsList) { + final String key = paramKV.first(); + final String value = paramKV.second(); + if ("cookie-name".equalsIgnoreCase(key)) { + cookieName = value; + } + if ("mode".equalsIgnoreCase(key)) { + mode = value; + } + if ("domain".equalsIgnoreCase(key)) { + if (domainSb == null) { + domainSb = new StringBuilder(); + } + domainSb = domainSb.append("domain "); + domainSb.append(value).append(" "); + } + if ("indirect".equalsIgnoreCase(key)) { + indirect = true; + } + if ("nocache".equalsIgnoreCase(key)) { + nocache = true; + } + if ("postonly".equalsIgnoreCase(key)) { + postonly = true; + } + } + if (cookieName == null) {// re-check all haproxy mandatory params + final StringBuilder tempSb = new StringBuilder(); + String srcip = lbTO.getSrcIp(); + if (srcip == null) { + srcip = "TESTCOOKIE"; + } + tempSb.append("lbcooki_").append(srcip.hashCode()).append("_").append(lbTO.getSrcPort()); + cookieName = tempSb.toString(); + } + sb.append("\t").append("cookie ").append(cookieName).append(" ").append(mode).append(" "); + if (indirect) { + sb.append("indirect "); + } + if (nocache) { + sb.append("nocache "); + } + if (postonly) { + sb.append("postonly "); + } + if (domainSb != null) { + sb.append(domainSb).append(" "); + } + } else if (StickinessMethodType.SourceBased.getName().equalsIgnoreCase(stickinessPolicy.getMethodName())) { + /* Default Values */ + String tablesize = "200k"; // optional + String expire = "30m"; // optional + + /* overwrite default values with the stick parameters */ + for (final Pair paramKV : paramsList) { + final String key = paramKV.first(); + final String value = paramKV.second(); + if ("tablesize".equalsIgnoreCase(key)) { + tablesize = value; + } + if ("expire".equalsIgnoreCase(key)) { + expire = value; + } + } + sb.append("\t").append("stick-table type ip size ").append(tablesize).append(" expire ").append(expire); + sb.append("\n\t").append("stick on src"); + } else if (StickinessMethodType.AppCookieBased.getName().equalsIgnoreCase(stickinessPolicy.getMethodName())) { + /* + * FORMAT : appsession len timeout + * [request-learn] [prefix] [mode + * ] + */ + /* example: appsession JSESSIONID len 52 timeout 3h */ + String cookieName = null; // optional + String length = "52"; // optional + String holdtime = "3h"; // optional + String mode = null; // optional + Boolean requestlearn = false; // optional + Boolean prefix = false; // optional + + for (final Pair paramKV : paramsList) { + final String key = paramKV.first(); + final String value = paramKV.second(); + if ("cookie-name".equalsIgnoreCase(key)) { + cookieName = value; + } + if ("length".equalsIgnoreCase(key)) { + length = value; + } + if ("holdtime".equalsIgnoreCase(key)) { + holdtime = value; + } + if ("mode".equalsIgnoreCase(key)) { + mode = value; + } + if ("request-learn".equalsIgnoreCase(key)) { + requestlearn = true; + } + if ("prefix".equalsIgnoreCase(key)) { + prefix = true; + } + } + if (cookieName == null) {// re-check all haproxy mandatory params + final StringBuilder tempSb = new StringBuilder(); + String srcip = lbTO.getSrcIp(); + if (srcip == null) { + srcip = "TESTCOOKIE"; + } + tempSb.append("appcookie_").append(srcip.hashCode()).append("_").append(lbTO.getSrcPort()); + cookieName = tempSb.toString(); + } + sb.append("\t").append("appsession ").append(cookieName).append(" len ").append(length).append(" timeout ").append(holdtime).append(" "); + if (prefix) { + sb.append("prefix "); + } + if (requestlearn) { + sb.append("request-learn").append(" "); + } + if (mode != null) { + sb.append("mode ").append(mode).append(" "); + } + } else { + /* + * Error is silently swallowed. + * Not supposed to reach here, validation of methods are + * done at the higher layer + */ + s_logger.warn("Haproxy stickiness policy for lb rule: " + lbTO.getSrcIp() + ":" + lbTO.getSrcPort() + ": Not Applied, cause:invalid method "); + return null; + } + } + if (i == 0) { + return null; + } + return sb.toString(); + } + + private List getRulesForPool(final LoadBalancerTO lbTO, final boolean keepAliveEnabled) { + StringBuilder sb = new StringBuilder(); + final String poolName = sb.append(lbTO.getSrcIp().replace(".", "_")).append('-').append(lbTO.getSrcPort()).toString(); + final String publicIP = lbTO.getSrcIp(); + final String publicPort = Integer.toString(lbTO.getSrcPort()); + final String algorithm = lbTO.getAlgorithm(); + + final List result = new ArrayList(); + // add line like this: "listen 65_37_141_30-80 65.37.141.30:80" + sb = new StringBuilder(); + sb.append("listen ").append(poolName).append(" ").append(publicIP).append(":").append(publicPort); + result.add(sb.toString()); + sb = new StringBuilder(); + sb.append("\t").append("balance ").append(algorithm); + result.add(sb.toString()); + + int i = 0; + Boolean destsAvailable = false; + final String stickinessSubRule = getLbSubRuleForStickiness(lbTO); + final List dstSubRule = new ArrayList(); + final List dstWithCookieSubRule = new ArrayList(); + for (final DestinationTO dest : lbTO.getDestinations()) { + // add line like this: "server 65_37_141_30-80_3 10.1.1.4:80 check" + if (dest.isRevoked()) { + continue; + } + sb = new StringBuilder(); + sb.append("\t") + .append("server ") + .append(poolName) + .append("_") + .append(Integer.toString(i++)) + .append(" ") + .append(dest.getDestIp()) + .append(":") + .append(dest.getDestPort()) + .append(" check"); + if(lbTO.getLbProtocol() != null && lbTO.getLbProtocol().equals("tcp-proxy")) { + sb.append(" send-proxy"); + } + dstSubRule.add(sb.toString()); + if (stickinessSubRule != null) { + sb.append(" cookie ").append(dest.getDestIp().replace(".", "_")).append('-').append(dest.getDestPort()).toString(); + dstWithCookieSubRule.add(sb.toString()); + } + destsAvailable = true; + } + + Boolean httpbasedStickiness = false; + /* attach stickiness sub rule only if the destinations are available */ + if (stickinessSubRule != null && destsAvailable == true) { + for (final StickinessPolicyTO stickinessPolicy : lbTO.getStickinessPolicies()) { + if (stickinessPolicy == null) { + continue; + } + if (StickinessMethodType.LBCookieBased.getName().equalsIgnoreCase(stickinessPolicy.getMethodName()) || + StickinessMethodType.AppCookieBased.getName().equalsIgnoreCase(stickinessPolicy.getMethodName())) { + httpbasedStickiness = true; + } + } + if (httpbasedStickiness) { + result.addAll(dstWithCookieSubRule); + } else { + result.addAll(dstSubRule); + } + result.add(stickinessSubRule); + } else { + result.addAll(dstSubRule); + } + if (stickinessSubRule != null && !destsAvailable) { + s_logger.warn("Haproxy stickiness policy for lb rule: " + lbTO.getSrcIp() + ":" + lbTO.getSrcPort() + ": Not Applied, cause: backends are unavailable"); + } + if (publicPort.equals(NetUtils.HTTP_PORT) && !keepAliveEnabled || httpbasedStickiness) { + sb = new StringBuilder(); + sb.append("\t").append("mode http"); + result.add(sb.toString()); + sb = new StringBuilder(); + sb.append("\t").append("option httpclose"); + result.add(sb.toString()); + } + + result.add(blankLine); + return result; + } + + private String generateStatsRule(final LoadBalancerConfigCommand lbCmd, final String ruleName, final String statsIp) { + final StringBuilder rule = new StringBuilder("\nlisten ").append(ruleName).append(" ").append(statsIp).append(":").append(lbCmd.lbStatsPort); + // TODO DH: write test for this in both cases + if (!lbCmd.keepAliveEnabled) { + s_logger.info("Haproxy mode http enabled"); + rule.append("\n\tmode http\n\toption httpclose"); + } + rule.append("\n\tstats enable\n\tstats uri ") + .append(lbCmd.lbStatsUri) + .append("\n\tstats realm Haproxy\\ Statistics\n\tstats auth ") + .append(lbCmd.lbStatsAuth); + rule.append("\n"); + final String result = rule.toString(); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Haproxystats rule: " + result); + } + return result; + } + + @Override + public String[] generateConfiguration(final LoadBalancerConfigCommand lbCmd) { + final List result = new ArrayList(); + final List gSection = Arrays.asList(globalSection); + // note that this is overwritten on the String in the static ArrayList + gSection.set(2, "\tmaxconn " + lbCmd.maxconn); + // TODO DH: write test for this function + final String pipesLine = "\tmaxpipes " + Long.toString(Long.parseLong(lbCmd.maxconn) / 4); + gSection.set(3, pipesLine); + if (s_logger.isDebugEnabled()) { + for (final String s : gSection) { + s_logger.debug("global section: " + s); + } + } + result.addAll(gSection); + // TODO decide under what circumstances these options are needed + // result.add("\tnokqueue"); + // result.add("\tnopoll"); + + result.add(blankLine); + final List dSection = Arrays.asList(defaultsSection); + if (lbCmd.keepAliveEnabled) { + dSection.set(7, "\tno option forceclose"); + } + + if (s_logger.isDebugEnabled()) { + for (final String s : dSection) { + s_logger.debug("default section: " + s); + } + } + result.addAll(dSection); + if (!lbCmd.lbStatsVisibility.equals("disabled")) { + /* new rule : listen admin_page guestip/link-local:8081 */ + if (lbCmd.lbStatsVisibility.equals("global")) { + result.add(generateStatsRule(lbCmd, "stats_on_public", lbCmd.lbStatsPublicIP)); + } else if (lbCmd.lbStatsVisibility.equals("guest-network")) { + result.add(generateStatsRule(lbCmd, "stats_on_guest", lbCmd.lbStatsGuestIP)); + } else if (lbCmd.lbStatsVisibility.equals("link-local")) { + result.add(generateStatsRule(lbCmd, "stats_on_private", lbCmd.lbStatsPrivateIP)); + } else if (lbCmd.lbStatsVisibility.equals("all")) { + result.add(generateStatsRule(lbCmd, "stats_on_public", lbCmd.lbStatsPublicIP)); + result.add(generateStatsRule(lbCmd, "stats_on_guest", lbCmd.lbStatsGuestIP)); + result.add(generateStatsRule(lbCmd, "stats_on_private", lbCmd.lbStatsPrivateIP)); + } else { + /* + * stats will be available on the default http serving port, no + * special stats port + */ + final StringBuilder subRule = + new StringBuilder("\tstats enable\n\tstats uri ").append(lbCmd.lbStatsUri) + .append("\n\tstats realm Haproxy\\ Statistics\n\tstats auth ") + .append(lbCmd.lbStatsAuth); + result.add(subRule.toString()); + } + + } + result.add(blankLine); + boolean has_listener = false; + for (final LoadBalancerTO lbTO : lbCmd.getLoadBalancers()) { + if (lbTO.isRevoked()) { + continue; + } + final List poolRules = getRulesForPool(lbTO, lbCmd.keepAliveEnabled); + result.addAll(poolRules); + has_listener = true; + } + result.add(blankLine); + if (!has_listener) { + // haproxy cannot handle empty listen / frontend or backend, so add + // a dummy listener + // on port 9 + result.addAll(Arrays.asList(defaultListen)); + } + return result.toArray(new String[result.size()]); + } + + @Override + public String[][] generateFwRules(final LoadBalancerConfigCommand lbCmd) { + final String[][] result = new String[3][]; + final Set toAdd = new HashSet(); + final Set toRemove = new HashSet(); + final Set toStats = new HashSet(); + + for (final LoadBalancerTO lbTO : lbCmd.getLoadBalancers()) { + + final StringBuilder sb = new StringBuilder(); + sb.append(lbTO.getSrcIp()).append(":"); + sb.append(lbTO.getSrcPort()).append(":"); + final String lbRuleEntry = sb.toString(); + if (!lbTO.isRevoked()) { + toAdd.add(lbRuleEntry); + } else { + toRemove.add(lbRuleEntry); + } + } + StringBuilder sb = new StringBuilder(""); + if (lbCmd.lbStatsVisibility.equals("guest-network")) { + sb = new StringBuilder(lbCmd.lbStatsGuestIP).append(":").append(lbCmd.lbStatsPort).append(":").append(lbCmd.lbStatsSrcCidrs).append(":,"); + } else if (lbCmd.lbStatsVisibility.equals("link-local")) { + sb = new StringBuilder(lbCmd.lbStatsPrivateIP).append(":").append(lbCmd.lbStatsPort).append(":").append(lbCmd.lbStatsSrcCidrs).append(":,"); + } else if (lbCmd.lbStatsVisibility.equals("global")) { + sb = new StringBuilder(lbCmd.lbStatsPublicIP).append(":").append(lbCmd.lbStatsPort).append(":").append(lbCmd.lbStatsSrcCidrs).append(":,"); + } else if (lbCmd.lbStatsVisibility.equals("all")) { + sb = new StringBuilder("0.0.0.0/0").append(":").append(lbCmd.lbStatsPort).append(":").append(lbCmd.lbStatsSrcCidrs).append(":,"); + } + toStats.add(sb.toString()); + + toRemove.removeAll(toAdd); + result[ADD] = toAdd.toArray(new String[toAdd.size()]); + result[REMOVE] = toRemove.toArray(new String[toRemove.size()]); + result[STATS] = toStats.toArray(new String[toStats.size()]); + + return result; + } +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/network/LoadBalancerConfigurator.java b/cosmic-core/nucleo/src/main/java/com/cloud/network/LoadBalancerConfigurator.java new file mode 100644 index 0000000000..0e19b1e606 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/network/LoadBalancerConfigurator.java @@ -0,0 +1,37 @@ +// +// 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.network; + +import java.util.List; + +import com.cloud.agent.api.routing.LoadBalancerConfigCommand; +import com.cloud.agent.api.to.PortForwardingRuleTO; + +public interface LoadBalancerConfigurator { + public final static int ADD = 0; + public final static int REMOVE = 1; + public final static int STATS = 2; + + public String[] generateConfiguration(List fwRules); + + public String[] generateConfiguration(LoadBalancerConfigCommand lbCmd); + + public String[][] generateFwRules(LoadBalancerConfigCommand lbCmd); +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/network/LoadBalancerValidator.java b/cosmic-core/nucleo/src/main/java/com/cloud/network/LoadBalancerValidator.java new file mode 100644 index 0000000000..98db543724 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/network/LoadBalancerValidator.java @@ -0,0 +1,31 @@ +// +// 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.network; + +import com.cloud.network.lb.LoadBalancingRule; + +public interface LoadBalancerValidator { + /** + * Validate rules + * @param rule + * @return true/false. If there are no validation then true should be return. + */ + public boolean validateLBRule(LoadBalancingRule rule); +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/network/resource/CreateLoadBalancerApplianceAnswer.java b/cosmic-core/nucleo/src/main/java/com/cloud/network/resource/CreateLoadBalancerApplianceAnswer.java new file mode 100644 index 0000000000..e631897445 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/network/resource/CreateLoadBalancerApplianceAnswer.java @@ -0,0 +1,75 @@ +// +// 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.network.resource; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.resource.ServerResource; + +public class CreateLoadBalancerApplianceAnswer extends Answer { + String deviceName; + String providerName; + ServerResource serverResource; + String username; + String password; + String publicInterface; + String privateInterface; + + public CreateLoadBalancerApplianceAnswer(Command cmd, boolean success, String details, String deviceName, String providerName, ServerResource serverResource, + String publicInterface, String privateInterface, String username, String password) { + this.deviceName = deviceName; + this.providerName = providerName; + this.serverResource = serverResource; + this.result = success; + this.details = details; + this.username = username; + this.password = password; + this.publicInterface = publicInterface; + this.privateInterface = privateInterface; + } + + public String getDeviceName() { + return deviceName; + } + + public String getProviderName() { + return providerName; + } + + public ServerResource getServerResource() { + return serverResource; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public String getPublicInterface() { + return publicInterface; + } + + public String getPrivateInterface() { + return privateInterface; + } +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/network/resource/DestroyLoadBalancerApplianceAnswer.java b/cosmic-core/nucleo/src/main/java/com/cloud/network/resource/DestroyLoadBalancerApplianceAnswer.java new file mode 100644 index 0000000000..80e2fe39c1 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/network/resource/DestroyLoadBalancerApplianceAnswer.java @@ -0,0 +1,30 @@ +// +// 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.network.resource; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; + +public class DestroyLoadBalancerApplianceAnswer extends Answer { + public DestroyLoadBalancerApplianceAnswer(Command cmd, boolean success, String details) { + this.result = success; + this.details = details; + } +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/network/resource/TrafficSentinelResource.java b/cosmic-core/nucleo/src/main/java/com/cloud/network/resource/TrafficSentinelResource.java new file mode 100644 index 0000000000..942187be33 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/network/resource/TrafficSentinelResource.java @@ -0,0 +1,345 @@ +// +// 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.network.resource; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +import javax.naming.ConfigurationException; + +import com.cloud.agent.IAgentControl; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.DirectNetworkUsageAnswer; +import com.cloud.agent.api.DirectNetworkUsageCommand; +import com.cloud.agent.api.MaintainAnswer; +import com.cloud.agent.api.MaintainCommand; +import com.cloud.agent.api.PingCommand; +import com.cloud.agent.api.ReadyAnswer; +import com.cloud.agent.api.ReadyCommand; +import com.cloud.agent.api.RecurringNetworkUsageAnswer; +import com.cloud.agent.api.RecurringNetworkUsageCommand; +import com.cloud.agent.api.StartupCommand; +import com.cloud.agent.api.StartupTrafficMonitorCommand; +import com.cloud.host.Host; +import com.cloud.resource.ServerResource; +import com.cloud.utils.exception.ExecutionException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TrafficSentinelResource implements ServerResource { + + private String _name; + private String _zoneId; + private String _ip; + private String _guid; + private String _url; + private String _inclZones; + private String _exclZones; + + private static final Logger s_logger = LoggerFactory.getLogger(TrafficSentinelResource.class); + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + try { + + _name = name; + + _zoneId = (String)params.get("zone"); + if (_zoneId == null) { + throw new ConfigurationException("Unable to find zone"); + } + + _ip = (String)params.get("ipaddress"); + if (_ip == null) { + throw new ConfigurationException("Unable to find IP"); + } + + _guid = (String)params.get("guid"); + if (_guid == null) { + throw new ConfigurationException("Unable to find the guid"); + } + + _url = (String)params.get("url"); + if (_url == null) { + throw new ConfigurationException("Unable to find url"); + } + + _inclZones = (String)params.get("inclZones"); + _exclZones = (String)params.get("exclZones"); + + return true; + } catch (Exception e) { + throw new ConfigurationException(e.getMessage()); + } + + } + + @Override + public StartupCommand[] initialize() { + StartupTrafficMonitorCommand cmd = new StartupTrafficMonitorCommand(); + cmd.setName(_name); + cmd.setDataCenter(_zoneId); + cmd.setPod(""); + cmd.setPrivateIpAddress(_ip); + cmd.setStorageIpAddress(""); + cmd.setVersion(TrafficSentinelResource.class.getPackage().getImplementationVersion()); + cmd.setGuid(_guid); + return new StartupCommand[] {cmd}; + } + + @Override + public Host.Type getType() { + return Host.Type.TrafficMonitor; + } + + @Override + public String getName() { + return _name; + } + + @Override + public PingCommand getCurrentStatus(final long id) { + return new PingCommand(Host.Type.TrafficMonitor, id); + } + + @Override + public boolean start() { + return true; + } + + @Override + public boolean stop() { + return true; + } + + @Override + public void disconnected() { + return; + } + + @Override + public IAgentControl getAgentControl() { + return null; + } + + @Override + public void setAgentControl(IAgentControl agentControl) { + return; + } + + @Override + public Answer executeRequest(Command cmd) { + if (cmd instanceof ReadyCommand) { + return execute((ReadyCommand)cmd); + } else if (cmd instanceof MaintainCommand) { + return execute((MaintainCommand)cmd); + } else if (cmd instanceof DirectNetworkUsageCommand) { + return execute((DirectNetworkUsageCommand)cmd); + } else if (cmd instanceof RecurringNetworkUsageCommand) { + return execute((RecurringNetworkUsageCommand)cmd); + } else { + return Answer.createUnsupportedCommandAnswer(cmd); + } + } + + private Answer execute(ReadyCommand cmd) { + return new ReadyAnswer(cmd); + } + + private synchronized RecurringNetworkUsageAnswer execute(RecurringNetworkUsageCommand cmd) { + return new RecurringNetworkUsageAnswer(cmd); + } + + private synchronized DirectNetworkUsageAnswer execute(DirectNetworkUsageCommand cmd) { + try { + return getPublicIpBytesSentAndReceived(cmd); + } catch (ExecutionException e) { + return new DirectNetworkUsageAnswer(cmd, e); + } + } + + private Answer execute(MaintainCommand cmd) { + return new MaintainAnswer(cmd); + } + + private DirectNetworkUsageAnswer getPublicIpBytesSentAndReceived(DirectNetworkUsageCommand cmd) throws ExecutionException { + DirectNetworkUsageAnswer answer = new DirectNetworkUsageAnswer(cmd); + + try { + //Direct Network Usage + URL trafficSentinel; + //Use Global include/exclude zones if there are no per TS zones + if (_inclZones == null) { + _inclZones = cmd.getIncludeZones(); + } + + if (_exclZones == null) { + _exclZones = cmd.getExcludeZones(); + } + + try { + //Query traffic Sentinel + trafficSentinel = + new URL(_url + "/inmsf/Query?script=" + URLEncoder.encode(getScript(cmd.getPublicIps(), cmd.getStart(), cmd.getEnd()), "UTF-8") + + "&authenticate=basic&resultFormat=txt"); + + BufferedReader in = new BufferedReader(new InputStreamReader(trafficSentinel.openStream())); + + String inputLine; + + while ((inputLine = in.readLine()) != null) { + //Parse the script output + StringTokenizer st = new StringTokenizer(inputLine, ","); + if (st.countTokens() == 3) { + String publicIp = st.nextToken(); + Long bytesSent = new Long(st.nextToken()); + Long bytesRcvd = new Long(st.nextToken()); + long[] bytesSentAndReceived = new long[2]; + bytesSentAndReceived[0] = bytesSent; + bytesSentAndReceived[1] = bytesRcvd; + answer.put(publicIp, bytesSentAndReceived); + } + } + in.close(); + } catch (MalformedURLException e1) { + s_logger.info("Invalid Traffic Sentinel URL", e1); + throw new ExecutionException(e1.getMessage()); + } catch (IOException e) { + s_logger.debug("Error in direct network usage accounting", e); + throw new ExecutionException(e.getMessage()); + } + } catch (Exception e) { + s_logger.debug(e.toString()); + throw new ExecutionException(e.getMessage()); + } + return answer; + } + + private String getScript(List ips, Date start, Date end) { + String IpAddresses = ""; + for (int i = 0; i < ips.size(); i++) { + IpAddresses += ips.get(i); + if (i != (ips.size() - 1)) { + // Append comma for all Ips except the last Ip + IpAddresses += ","; + } + } + String destZoneCondition = ""; + if (_inclZones != null && !_inclZones.isEmpty()) { + destZoneCondition = " & destinationzone = " + _inclZones; + } + if (_exclZones != null && !_exclZones.isEmpty()) { + destZoneCondition += " & destinationzone != " + _exclZones; + } + + String srcZoneCondition = ""; + if (_inclZones != null && !_inclZones.isEmpty()) { + srcZoneCondition = " & sourcezone = " + _inclZones; + } + if (_exclZones != null && !_exclZones.isEmpty()) { + srcZoneCondition += " & sourcezone != " + _exclZones; + } + + String startDate = getDateString(start); + String endtDate = getDateString(end); + StringBuffer sb = new StringBuffer(); + sb.append("var q = Query.topN(\"historytrmx\","); + sb.append(" \"ipsource,bytes\","); + sb.append(" \"ipsource = " + IpAddresses + destZoneCondition + "\","); + sb.append(" \"" + startDate + ", " + endtDate + "\","); + sb.append(" \"bytes\","); + sb.append(" 100000);"); + sb.append("var totalsSent = {};"); + sb.append("var t = q.run("); + sb.append(" function(row,table) {"); + sb.append(" if(row[0]) { "); + sb.append(" totalsSent[row[0]] = row[1];"); + sb.append(" }"); + sb.append(" });"); + sb.append("var q = Query.topN(\"historytrmx\","); + sb.append(" \"ipdestination,bytes\","); + sb.append(" \"ipdestination = " + IpAddresses + srcZoneCondition + "\","); + sb.append(" \"" + startDate + ", " + endtDate + "\","); + sb.append(" \"bytes\","); + sb.append(" 100000);"); + sb.append("var totalsRcvd = {};"); + sb.append("var t = q.run("); + sb.append(" function(row,table) {"); + sb.append(" if(row[0]) {"); + sb.append(" totalsRcvd[row[0]] = row[1];"); + sb.append(" }"); + sb.append(" });"); + sb.append("for (var addr in totalsSent) {"); + sb.append(" var TS = 0;"); + sb.append(" var TR = 0;"); + sb.append(" if(totalsSent[addr]) TS = totalsSent[addr];"); + sb.append(" if(totalsRcvd[addr]) TR = totalsRcvd[addr];"); + sb.append(" println(addr + \",\" + TS + \",\" + TR);"); + sb.append("}"); + return sb.toString(); + } + + private String getDateString(Date date) { + DateFormat dfDate = new SimpleDateFormat("yyyyMMdd HH:mm:ss"); + return dfDate.format(date); + } + + @Override + public void setName(String name) { + // TODO Auto-generated method stub + + } + + @Override + public void setConfigParams(Map params) { + // TODO Auto-generated method stub + + } + + @Override + public Map getConfigParams() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getRunLevel() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void setRunLevel(int level) { + // TODO Auto-generated method stub + + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/resource/CommandWrapper.java b/cosmic-core/nucleo/src/main/java/com/cloud/resource/CommandWrapper.java new file mode 100644 index 0000000000..f8596998b1 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/resource/CommandWrapper.java @@ -0,0 +1,33 @@ +// +// 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.resource; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; + +public abstract class CommandWrapper { + + /** + * @param T is the command to be used. + * @param R is the resource base to be used. + * @return A and the Answer from the command. + */ + public abstract A execute(T command, R serverResource); +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/resource/RequestWrapper.java b/cosmic-core/nucleo/src/main/java/com/cloud/resource/RequestWrapper.java new file mode 100644 index 0000000000..29520c407d --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/resource/RequestWrapper.java @@ -0,0 +1,146 @@ +// +// 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.resource; + +import java.text.MessageFormat; +import java.util.Hashtable; +import java.util.Set; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class RequestWrapper { + + private static final Logger s_logger = LoggerFactory.getLogger(RequestWrapper.class); + + @SuppressWarnings("rawtypes") + protected Hashtable, Hashtable, CommandWrapper>> resources = new Hashtable, Hashtable, CommandWrapper>>(); + + /** + * @param command to be executed. + * @return an Answer for the executed command. + */ + public abstract Answer execute(Command command, ServerResource serverResource); + + @SuppressWarnings({ "rawtypes", "unchecked" }) + protected Hashtable, CommandWrapper> retrieveResource(final Command command, final Class resourceClass) { + Class keepResourceClass = resourceClass; + Hashtable, CommandWrapper> resource = resources.get(keepResourceClass); + while (resource == null) { + try { + final Class keepResourceClass2 = (Class) keepResourceClass.getSuperclass(); + resource = resources.get(keepResourceClass2); + + keepResourceClass = keepResourceClass2; + } catch (final ClassCastException e) { + throw new NullPointerException("No key found for '" + command.getClass() + "' in the Map!"); + } + } + return resource; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + protected CommandWrapper retrieveCommands(final Class commandClass, + final Hashtable, CommandWrapper> resourceCommands) { + + Class keepCommandClass = commandClass; + CommandWrapper commandWrapper = resourceCommands.get(keepCommandClass); + while (commandWrapper == null) { + try { + final Class commandClass2 = (Class) keepCommandClass.getSuperclass(); + + if (commandClass2 == null) { + throw new NullPointerException("All the COMMAND hierarchy tree has been visited but no compliant key has been found for '" + commandClass + "'."); + } + + commandWrapper = resourceCommands.get(commandClass2); + + keepCommandClass = commandClass2; + } catch (final ClassCastException e) { + throw new NullPointerException("No key found for '" + keepCommandClass.getClass() + "' in the Map!"); + } catch (final NullPointerException e) { + // Will now traverse all the resource hierarchy. Returning null + // is not a problem. + // It is all being nicely checked and in case we do not have a + // resource, an Unsupported answer will be thrown by the base + // class. + return null; + } + } + return commandWrapper; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + protected CommandWrapper retryWhenAllFails(final Command command, final Class resourceClass, + final Hashtable, CommandWrapper> resourceCommands) { + + Class keepResourceClass = resourceClass; + CommandWrapper commandWrapper = resourceCommands.get(command.getClass()); + while (commandWrapper == null) { + // Could not find the command in the given resource, will traverse + // the family tree. + try { + final Class resourceClass2 = (Class) keepResourceClass.getSuperclass(); + + if (resourceClass2 == null) { + throw new NullPointerException("All the SERVER-RESOURCE hierarchy tree has been visited but no compliant key has been found for '" + command.getClass() + "'."); + } + + final Hashtable, CommandWrapper> resourceCommands2 = retrieveResource(command, + (Class) keepResourceClass.getSuperclass()); + keepResourceClass = resourceClass2; + + commandWrapper = retrieveCommands(command.getClass(), resourceCommands2); + } catch (final ClassCastException e) { + throw new NullPointerException("No key found for '" + command.getClass() + "' in the Map!"); + } catch (final NullPointerException e) { + throw e; + } + } + return commandWrapper; + } + + @SuppressWarnings("rawtypes") + protected Hashtable, CommandWrapper> processAnnotations(final Set> wrappers) { + final String errorMessage = "Error when adding Xen command to map ==> '{0}'. CommandWrapper class is ==> '{1}'"; + + final Hashtable, CommandWrapper> commands = new Hashtable, CommandWrapper>(); + + for (final Class wrapper : wrappers) { + final ResourceWrapper annotation = wrapper.getAnnotation(ResourceWrapper.class); + if (annotation == null) { + // Just in case people add classes without the annotation in the package and we don't see it. + continue; + } + try { + commands.put(annotation.handles(), wrapper.newInstance()); + } catch (final InstantiationException e) { + s_logger.warn(MessageFormat.format(errorMessage, e.getLocalizedMessage(), wrapper.toString())); + } catch (final IllegalAccessException e) { + s_logger.warn(MessageFormat.format(errorMessage, e.getLocalizedMessage(), wrapper.toString())); + } + } + + return commands; + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/resource/ResourceListener.java b/cosmic-core/nucleo/src/main/java/com/cloud/resource/ResourceListener.java new file mode 100644 index 0000000000..968217db34 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/resource/ResourceListener.java @@ -0,0 +1,109 @@ +// +// 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.resource; + +import java.net.URI; +import java.util.List; +import java.util.Map; + +import com.cloud.host.Host; + +public interface ResourceListener { + static final Integer EVENT_DISCOVER_BEFORE = 0x1; + static final Integer EVENT_DISCOVER_AFTER = 0x1 << 1; + static final Integer EVENT_DELETE_HOST_BEFORE = 0x1 << 2; + static final Integer EVENT_DELETE_HOST_AFTER = 0x1 << 3; + static final Integer EVENT_CANCEL_MAINTENANCE_BEFORE = 0x1 << 4; + static final Integer EVENT_CANCEL_MAINTENANCE_AFTER = 0x1 << 5; + static final Integer EVENT_PREPARE_MAINTENANCE_BEFORE = 0x1 << 6; + static final Integer EVENT_PREPARE_MAINTENANCE_AFTER = 0x1 << 7; + static final Integer EVENT_ALL = (EVENT_DISCOVER_BEFORE | EVENT_DISCOVER_AFTER | EVENT_DELETE_HOST_BEFORE | EVENT_DELETE_HOST_AFTER | + EVENT_CANCEL_MAINTENANCE_BEFORE | EVENT_CANCEL_MAINTENANCE_AFTER | EVENT_PREPARE_MAINTENANCE_BEFORE | EVENT_PREPARE_MAINTENANCE_AFTER); + + /** + * + * @param dcid + * @param podId + * @param clusterId + * @param uri + * @param username + * @param password + * @param hostTags + * + * Called before Discover.find() + */ + void processDiscoverEventBefore(Long dcid, Long podId, Long clusterId, URI uri, String username, String password, List hostTags); + + /** + * + * @param resources + * + * Called after Discover.find() + */ + void processDiscoverEventAfter(Map> resources); + + /** + * + * @param host + * + * Called before host delete + */ + void processDeleteHostEventBefore(Host host); + + /** + * + * @param host + * + * Called after host delete. NOTE param host includes stale data which has been removed from database + */ + void processDeletHostEventAfter(Host host); + + /** + * + * @param hostId + * + * Called before AgentManager.cancelMaintenance + */ + void processCancelMaintenaceEventBefore(Long hostId); + + /** + * + * @param hostId + * + * Called after AgentManager.cancelMaintenance + */ + void processCancelMaintenaceEventAfter(Long hostId); + + /** + * + * @param hostId + * + * Called before AgentManager.main + */ + void processPrepareMaintenaceEventBefore(Long hostId); + + /** + * + * @param hostId + * + * Called after AgentManager.main + */ + void processPrepareMaintenaceEventAfter(Long hostId); +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/resource/ResourceWrapper.java b/cosmic-core/nucleo/src/main/java/com/cloud/resource/ResourceWrapper.java new file mode 100644 index 0000000000..1ea0b8d8c9 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/resource/ResourceWrapper.java @@ -0,0 +1,35 @@ +// +// 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.resource; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.cloud.agent.api.Command; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface ResourceWrapper { + + Class handles(); + +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/resource/ServerResource.java b/cosmic-core/nucleo/src/main/java/com/cloud/resource/ServerResource.java new file mode 100644 index 0000000000..9030db72f0 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/resource/ServerResource.java @@ -0,0 +1,73 @@ +// +// 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.resource; + +import com.cloud.agent.IAgentControl; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.PingCommand; +import com.cloud.agent.api.StartupCommand; +import com.cloud.host.Host; +import com.cloud.utils.component.Manager; + +/** + * ServerResource is a generic container to execute commands sent + */ +public interface ServerResource extends Manager { + /** + * @return Host.Type type of the computing server we have. + */ + Host.Type getType(); + + /** + * Generate a startup command containing information regarding the resource. + * @return StartupCommand ready to be sent to the management server. + */ + StartupCommand[] initialize(); + + /** + * @param id id of the server to put in the PingCommand + * @return PingCommand + */ + PingCommand getCurrentStatus(long id); + + /** + * Execute the request coming from the computing server. + * @param cmd Command to execute. + * @return Answer + */ + Answer executeRequest(Command cmd); + + /** + * disconnected() is called when the connection is down between the + * agent and the management server. If there are any cleanups, this + * is the time to do it. + */ + void disconnected(); + + /** + * This is added to allow calling agent control service from within the resource + * @return + */ + IAgentControl getAgentControl(); + + void setAgentControl(IAgentControl agentControl); + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/resource/ServerResourceBase.java b/cosmic-core/nucleo/src/main/java/com/cloud/resource/ServerResourceBase.java new file mode 100644 index 0000000000..0d495b0e94 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/resource/ServerResourceBase.java @@ -0,0 +1,310 @@ +// +// 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.resource; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.Date; +import java.util.Enumeration; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import javax.naming.ConfigurationException; + +import com.cloud.agent.IAgentControl; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.StartupCommand; +import com.cloud.utils.net.NetUtils; +import com.cloud.utils.script.Script; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class ServerResourceBase implements ServerResource { + private static final Logger s_logger = LoggerFactory.getLogger(ServerResourceBase.class); + protected String _name; + private ArrayList _warnings = new ArrayList(); + private ArrayList _errors = new ArrayList(); + protected NetworkInterface _publicNic; + protected NetworkInterface _privateNic; + protected NetworkInterface _storageNic; + protected NetworkInterface _storageNic2; + protected IAgentControl _agentControl; + + @Override + public String getName() { + return _name; + } + + protected String findScript(String script) { + return Script.findScript(getDefaultScriptsDir(), script); + } + + protected abstract String getDefaultScriptsDir(); + + @Override + public boolean configure(final String name, Map params) throws ConfigurationException { + _name = name; + + String publicNic = (String)params.get("public.network.device"); + if (publicNic == null) { + publicNic = "xenbr1"; + } + String privateNic = (String)params.get("private.network.device"); + if (privateNic == null) { + privateNic = "xenbr0"; + } + final String storageNic = (String)params.get("storage.network.device"); + final String storageNic2 = (String)params.get("storage.network.device.2"); + + _privateNic = getNetworkInterface(privateNic); + _publicNic = getNetworkInterface(publicNic); + _storageNic = getNetworkInterface(storageNic); + _storageNic2 = getNetworkInterface(storageNic2); + + if (_privateNic == null) { + s_logger.warn("Nics are not specified in properties file/db, will try to autodiscover"); + + Enumeration nics = null; + try { + nics = NetworkInterface.getNetworkInterfaces(); + if (nics == null || !nics.hasMoreElements()) { + throw new ConfigurationException("Private NIC is not configured"); + } + } catch (final SocketException e) { + throw new ConfigurationException("Private NIC is not configured"); + } + + while (nics.hasMoreElements()) { + final NetworkInterface nic = nics.nextElement(); + final String nicName = nic.getName(); + // try { + if (//!nic.isLoopback() && + //nic.isUp() && + !nic.isVirtual() && !nicName.startsWith("vnif") && !nicName.startsWith("vnbr") && !nicName.startsWith("peth") && !nicName.startsWith("vif") && + !nicName.startsWith("virbr") && !nicName.contains(":")) { + final String[] info = NetUtils.getNicParams(nicName); + if (info != null && info[0] != null) { + _privateNic = nic; + s_logger.info("Designating private to be nic " + nicName); + break; + } + } + // } catch (final SocketException e) { + // s_logger.warn("Error looking at " + nicName, e); + // } + s_logger.debug("Skipping nic " + nicName); + } + + if (_privateNic == null) { + throw new ConfigurationException("Private NIC is not configured"); + } + } + String infos[] = NetUtils.getNetworkParams(_privateNic); + if (infos == null) { + s_logger.warn("Incorrect details for private Nic during initialization of ServerResourceBase"); + return false; + } + params.put("host.ip", infos[0]); + params.put("host.mac.address", infos[1]); + + return true; + } + + protected NetworkInterface getNetworkInterface(String nicName) { + s_logger.debug("Retrieving network interface: " + nicName); + if (nicName == null) { + return null; + } + + if (nicName.trim().length() == 0) { + return null; + } + + nicName = nicName.trim(); + + NetworkInterface nic; + try { + nic = NetworkInterface.getByName(nicName); + if (nic == null) { + s_logger.debug("Unable to get network interface for " + nicName); + return null; + } + + return nic; + } catch (final SocketException e) { + s_logger.warn("Unable to get network interface for " + nicName, e); + return null; + } + } + + protected void fillNetworkInformation(final StartupCommand cmd) { + String[] info = null; + if (_privateNic != null) { + info = NetUtils.getNetworkParams(_privateNic); + if (info != null) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Parameters for private nic: " + info[0] + " - " + info[1] + "-" + info[2]); + } + cmd.setPrivateIpAddress(info[0]); + cmd.setPrivateMacAddress(info[1]); + cmd.setPrivateNetmask(info[2]); + } + } + + if (_storageNic != null) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Storage has its now nic: " + _storageNic.getName()); + } + info = NetUtils.getNetworkParams(_storageNic); + } + + // NOTE: In case you're wondering, this is not here by mistake. + if (info != null) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Parameters for storage nic: " + info[0] + " - " + info[1] + "-" + info[2]); + } + cmd.setStorageIpAddress(info[0]); + cmd.setStorageMacAddress(info[1]); + cmd.setStorageNetmask(info[2]); + } + + if (_publicNic != null) { + info = NetUtils.getNetworkParams(_publicNic); + if (info != null) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Parameters for public nic: " + info[0] + " - " + info[1] + "-" + info[2]); + } + cmd.setPublicIpAddress(info[0]); + cmd.setPublicMacAddress(info[1]); + cmd.setPublicNetmask(info[2]); + } + } + + if (_storageNic2 != null) { + info = NetUtils.getNetworkParams(_storageNic2); + if (info != null) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Parameters for storage nic 2: " + info[0] + " - " + info[1] + "-" + info[2]); + } + cmd.setStorageIpAddressDeux(info[0]); + cmd.setStorageMacAddressDeux(info[1]); + cmd.setStorageNetmaskDeux(info[2]); + } + } + } + + @Override + public void disconnected() { + } + + @Override + public IAgentControl getAgentControl() { + return _agentControl; + } + + @Override + public void setAgentControl(IAgentControl agentControl) { + _agentControl = agentControl; + } + + protected void recordWarning(final String msg, final Throwable th) { + final String str = getLogStr(msg, th); + synchronized (_warnings) { + _warnings.add(str); + } + } + + protected void recordWarning(final String msg) { + recordWarning(msg, null); + } + + protected List getWarnings() { + synchronized (_warnings) { + final List results = new LinkedList(_warnings); + _warnings.clear(); + return results; + } + } + + protected List getErrors() { + synchronized (_errors) { + final List result = new LinkedList(_errors); + _errors.clear(); + return result; + } + } + + protected void recordError(final String msg, final Throwable th) { + final String str = getLogStr(msg, th); + synchronized (_errors) { + _errors.add(str); + } + } + + protected void recordError(final String msg) { + recordError(msg, null); + } + + protected Answer createErrorAnswer(final Command cmd, final String msg, final Throwable th) { + final StringWriter writer = new StringWriter(); + if (msg != null) { + writer.append(msg); + } + writer.append("===>Stack<==="); + th.printStackTrace(new PrintWriter(writer)); + return new Answer(cmd, false, writer.toString()); + } + + protected String createErrorDetail(final String msg, final Throwable th) { + final StringWriter writer = new StringWriter(); + if (msg != null) { + writer.append(msg); + } + writer.append("===>Stack<==="); + th.printStackTrace(new PrintWriter(writer)); + return writer.toString(); + } + + protected String getLogStr(final String msg, final Throwable th) { + final StringWriter writer = new StringWriter(); + writer.append(new Date().toString()).append(": ").append(msg); + if (th != null) { + writer.append("\n Exception: "); + th.printStackTrace(new PrintWriter(writer)); + } + return writer.toString(); + } + + @Override + public boolean start() { + return true; + } + + @Override + public boolean stop() { + return true; + } +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/resource/hypervisor/HypervisorResource.java b/cosmic-core/nucleo/src/main/java/com/cloud/resource/hypervisor/HypervisorResource.java new file mode 100644 index 0000000000..dd55b04d4f --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/resource/hypervisor/HypervisorResource.java @@ -0,0 +1,56 @@ +// +// 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.resource.hypervisor; + +import com.cloud.agent.api.RebootAnswer; +import com.cloud.agent.api.RebootCommand; +import com.cloud.agent.api.StartAnswer; +import com.cloud.agent.api.StartCommand; +import com.cloud.agent.api.StopAnswer; +import com.cloud.agent.api.StopCommand; +import com.cloud.resource.ServerResource; + +/** + * HypervisorResource specifies all of the commands a hypervisor agent needs + * + */ +public interface HypervisorResource extends ServerResource { + /** + * Starts a VM. All information regarding the VM + * are carried within the command. + * @param cmd carries all the information necessary to start a VM + * @return Start2Answer answer. + */ + StartAnswer execute(StartCommand cmd); + + /** + * Stops a VM. Must return true as long as the VM does not exist. + * @param cmd information necessary to identify the VM to stop. + * @return StopAnswer + */ + StopAnswer execute(StopCommand cmd); + + /** + * Reboots a VM. + * @param cmd information necessary to identify the VM to reboot. + * @return RebootAnswer + */ + RebootAnswer execute(RebootCommand cmd); +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/serializer/GsonHelper.java b/cosmic-core/nucleo/src/main/java/com/cloud/serializer/GsonHelper.java new file mode 100644 index 0000000000..2ae5ee04a9 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/serializer/GsonHelper.java @@ -0,0 +1,90 @@ +// +// 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.serializer; + +import java.util.List; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.SecStorageFirewallCfgCommand.PortConfig; +import com.cloud.agent.api.to.DataStoreTO; +import com.cloud.agent.api.to.DataTO; +import com.cloud.agent.transport.ArrayTypeAdaptor; +import com.cloud.agent.transport.InterfaceTypeAdaptor; +import com.cloud.agent.transport.LoggingExclusionStrategy; +import com.cloud.agent.transport.Request.NwGroupsCommandTypeAdaptor; +import com.cloud.agent.transport.Request.PortConfigListTypeAdaptor; +import com.cloud.utils.Pair; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; + +import org.apache.log4j.Logger; + +public class GsonHelper { + private static final Logger s_logger = Logger.getLogger(GsonHelper.class); + + protected static final Gson s_gson; + protected static final Gson s_gogger; + + static { + GsonBuilder gsonBuilder = new GsonBuilder(); + s_gson = setDefaultGsonConfig(gsonBuilder); + GsonBuilder loggerBuilder = new GsonBuilder(); + loggerBuilder.disableHtmlEscaping(); + loggerBuilder.setExclusionStrategies(new LoggingExclusionStrategy(s_logger)); + s_gogger = setDefaultGsonConfig(loggerBuilder); + s_logger.info("Default Builder inited."); + } + + static Gson setDefaultGsonConfig(GsonBuilder builder) { + builder.setVersion(1.5); + InterfaceTypeAdaptor dsAdaptor = new InterfaceTypeAdaptor(); + builder.registerTypeAdapter(DataStoreTO.class, dsAdaptor); + InterfaceTypeAdaptor dtAdaptor = new InterfaceTypeAdaptor(); + builder.registerTypeAdapter(DataTO.class, dtAdaptor); + ArrayTypeAdaptor cmdAdaptor = new ArrayTypeAdaptor(); + builder.registerTypeAdapter(Command[].class, cmdAdaptor); + ArrayTypeAdaptor ansAdaptor = new ArrayTypeAdaptor(); + builder.registerTypeAdapter(Answer[].class, ansAdaptor); + builder.registerTypeAdapter(new TypeToken>() { + }.getType(), new PortConfigListTypeAdaptor()); + builder.registerTypeAdapter(new TypeToken>() { + }.getType(), new NwGroupsCommandTypeAdaptor()); + Gson gson = builder.create(); + dsAdaptor.initGson(gson); + dtAdaptor.initGson(gson); + cmdAdaptor.initGson(gson); + ansAdaptor.initGson(gson); + return gson; + } + + public final static Gson getGson() { + return s_gson; + } + + public final static Gson getGsonLogger() { + return s_gogger; + } + + public final static Logger getLogger() { + return s_logger; + } +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/serializer/SerializerHelper.java b/cosmic-core/nucleo/src/main/java/com/cloud/serializer/SerializerHelper.java new file mode 100644 index 0000000000..67b79f17a4 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/serializer/SerializerHelper.java @@ -0,0 +1,193 @@ +// +// 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.serializer; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import com.cloud.utils.DateUtil; +import com.cloud.utils.Pair; +import com.google.gson.Gson; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Note: toPairList and appendPairList only support simple POJO objects currently + */ +public class SerializerHelper { + public static final Logger s_logger = LoggerFactory.getLogger(SerializerHelper.class.getName()); + public static final String token = "/"; + + public static String toSerializedStringOld(Object result) { + if (result != null) { + Class clz = result.getClass(); + Gson gson = GsonHelper.getGson(); + return clz.getName() + token + gson.toJson(result); + } + return null; + } + + public static Object fromSerializedString(String result) { + try { + if (result != null && !result.isEmpty()) { + + String[] serializedParts = result.split(token); + + if (serializedParts.length < 2) { + return null; + } + String clzName = serializedParts[0]; + String nameField = null; + String content = null; + if (serializedParts.length == 2) { + content = serializedParts[1]; + } else { + nameField = serializedParts[1]; + int index = result.indexOf(token + nameField + token); + content = result.substring(index + nameField.length() + 2); + } + + Class clz; + try { + clz = Class.forName(clzName); + } catch (ClassNotFoundException e) { + return null; + } + + Gson gson = GsonHelper.getGson(); + Object obj = gson.fromJson(content, clz); + return obj; + } + return null; + } catch (RuntimeException e) { + s_logger.error("Caught runtime exception when doing GSON deserialization on: " + result); + throw e; + } + } + + public static List> toPairList(Object o, String name) { + List> l = new ArrayList>(); + return appendPairList(l, o, name); + } + + public static List> appendPairList(List> l, Object o, String name) { + if (o != null) { + Class clz = o.getClass(); + + if (clz.isPrimitive() || clz.getSuperclass() == Number.class || clz == String.class || clz == Date.class) { + l.add(new Pair(name, o.toString())); + return l; + } + + for (Field f : clz.getDeclaredFields()) { + if ((f.getModifiers() & Modifier.STATIC) != 0) { + continue; + } + + Param param = f.getAnnotation(Param.class); + if (param == null) { + continue; + } + + String propName = f.getName(); + if (!param.propName().isEmpty()) { + propName = param.propName(); + } + + String paramName = param.name(); + if (paramName.isEmpty()) { + paramName = propName; + } + + Method method = getGetMethod(o, propName); + if (method != null) { + try { + Object fieldValue = method.invoke(o); + if (fieldValue != null) { + if (f.getType() == Date.class) { + l.add(new Pair(paramName, DateUtil.getOutputString((Date)fieldValue))); + } else { + l.add(new Pair(paramName, fieldValue.toString())); + } + } + //else + // l.add(new Pair(paramName, "")); + } catch (IllegalArgumentException e) { + s_logger.error("Illegal argument exception when calling POJO " + o.getClass().getName() + " get method for property: " + propName); + + } catch (IllegalAccessException e) { + s_logger.error("Illegal access exception when calling POJO " + o.getClass().getName() + " get method for property: " + propName); + } catch (InvocationTargetException e) { + s_logger.error("Invocation target exception when calling POJO " + o.getClass().getName() + " get method for property: " + propName); + } + } + } + } + return l; + } + + private static Method getGetMethod(Object o, String propName) { + Method method = null; + String methodName = getGetMethodName("get", propName); + try { + method = o.getClass().getMethod(methodName); + } catch (SecurityException e1) { + s_logger.error("Security exception in getting POJO " + o.getClass().getName() + " get method for property: " + propName); + } catch (NoSuchMethodException e1) { + if (s_logger.isTraceEnabled()) { + s_logger.trace("POJO " + o.getClass().getName() + " does not have " + methodName + "() method for property: " + propName + + ", will check is-prefixed method to see if it is boolean property"); + } + } + + if (method != null) { + return method; + } + + methodName = getGetMethodName("is", propName); + try { + method = o.getClass().getMethod(methodName); + } catch (SecurityException e1) { + s_logger.error("Security exception in getting POJO " + o.getClass().getName() + " get method for property: " + propName); + } catch (NoSuchMethodException e1) { + s_logger.warn("POJO " + o.getClass().getName() + " does not have " + methodName + "() method for property: " + propName); + } + return method; + } + + private static String getGetMethodName(String prefix, String fieldName) { + StringBuffer sb = new StringBuffer(prefix); + + if (fieldName.length() >= prefix.length() && fieldName.substring(0, prefix.length()).equals(prefix)) { + return fieldName; + } else { + sb.append(fieldName.substring(0, 1).toUpperCase()); + sb.append(fieldName.substring(1)); + } + + return sb.toString(); + } +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/storage/JavaStorageLayer.java b/cosmic-core/nucleo/src/main/java/com/cloud/storage/JavaStorageLayer.java new file mode 100644 index 0000000000..2e5544c8c1 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/storage/JavaStorageLayer.java @@ -0,0 +1,315 @@ +// +// 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.storage; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import javax.ejb.Local; +import javax.naming.ConfigurationException; + +@Local(value = StorageLayer.class) +public class JavaStorageLayer implements StorageLayer { + + String _name; + boolean _makeWorldWriteable = true; + + public JavaStorageLayer() { + super(); + } + + public JavaStorageLayer(boolean makeWorldWriteable) { + this(); + _makeWorldWriteable = makeWorldWriteable; + } + + @Override + public boolean cleanup(String path, String rootPath) throws IOException { + assert path.startsWith(rootPath) : path + " does not start with " + rootPath; + + synchronized (path) { + File file = new File(path); + if (!file.delete()) { + return false; + } + int index = -1; + long rootLength = rootPath.length(); + while ((index = path.lastIndexOf(File.separator)) != -1 && path.length() > rootLength) { + file = new File(path.substring(0, index)); + String[] children = file.list(); + if (children != null && children.length > 0) { + break; + } + if (!file.delete()) { + throw new IOException("Unable to delete " + file.getAbsolutePath()); + } + } + return true; + } + } + + @Override + public boolean create(String path, String filename) throws IOException { + synchronized (path.intern()) { + String newFile = path + File.separator + filename; + File file = new File(newFile); + if (file.exists()) { + return true; + } + + return file.createNewFile(); + } + } + + @Override + public boolean delete(String path) { + synchronized (path.intern()) { + File file = new File(path); + return file.delete(); + } + } + + @Override + public boolean deleteDir(String dir) { + File Dir = new File(dir); + if (!Dir.isDirectory()) { + return false; + } + + synchronized (dir.intern()) { + File[] files = Dir.listFiles(); + for (File file : files) { + if (!file.delete()) { + return false; + } + } + } + return true; + } + + @Override + public boolean exists(String path) { + synchronized (path.intern()) { + File file = new File(path); + return file.exists(); + } + } + + @Override + public long getTotalSpace(String path) { + File file = new File(path); + return file.getTotalSpace(); + } + + @Override + public long getUsableSpace(String path) { + File file = new File(path); + return file.getUsableSpace(); + } + + @Override + public String[] listFiles(String path) { + File file = new File(path); + File[] files = file.listFiles(); + if (files == null) { + return new String[0]; + } + String[] paths = new String[files.length]; + for (int i = 0; i < files.length; i++) { + paths[i] = files[i].getAbsolutePath(); + } + return paths; + } + + @Override + public List listMountPointsByMsHost(String path, long msHostId) { + List mountPaths = new ArrayList(); + File[] files = new File(path).listFiles(); + if (files == null) { + return mountPaths; + } + for (File file : files) { + if (file.getName().startsWith(String.valueOf(msHostId) + ".")) + mountPaths.add(file.getAbsolutePath()); + } + return mountPaths; + } + + @Override + public boolean mkdir(String path) { + synchronized (path.intern()) { + File file = new File(path); + + if (file.exists()) { + return file.isDirectory(); + } + if (_makeWorldWriteable) { + return (file.mkdirs() && setWorldReadableAndWriteable(file)); + } else { + return file.mkdirs(); + } + } + } + + @Override + public long getSize(String path) { + File file = new File(path); + return file.length(); + } + + @Override + public File createUniqDir() { + String dirName = System.getProperty("java.io.tmpdir"); + if (dirName != null) { + File dir = new File(dirName); + if (dir.exists()) { + String uniqDirName = dir.getAbsolutePath() + File.separator + UUID.randomUUID().toString(); + if (mkdir(uniqDirName)) { + return new File(uniqDirName); + } + } + } + return null; + } + + @Override + public boolean mkdirs(String path) { + synchronized (path.intern()) { + File dir = new File(path); + + if (dir.exists()) { + return dir.isDirectory(); + } + + boolean success = true; + List dirPaths = listDirPaths(path); + for (String dirPath : dirPaths) { + dir = new File(dirPath); + if (!dir.exists()) { + success = dir.mkdir(); + if (_makeWorldWriteable) + success = success && setWorldReadableAndWriteable(dir); + } + } + + return success; + } + } + + private List listDirPaths(String path) { + String[] dirNames = path.split("/"); + List dirPaths = new ArrayList(); + + String currentPath = ""; + for (int i = 0; i < dirNames.length; i++) { + String currentName = dirNames[i].trim(); + if (!currentName.isEmpty()) { + currentPath += "/" + currentName; + dirPaths.add(currentPath); + } + } + + return dirPaths; + } + + @Override + public boolean setWorldReadableAndWriteable(File file) { + return (file.setReadable(true, false) && file.setWritable(true, false)); + } + + @Override + public boolean isDirectory(String path) { + File file = new File(path); + return file.isDirectory(); + } + + @Override + public boolean isFile(String path) { + File file = new File(path); + return file.isFile(); + } + + @Override + public File getFile(String path) { + return new File(path); + } + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + _name = name; + return true; + } + + @Override + public String getName() { + return _name; + } + + @Override + public boolean start() { + return true; + } + + @Override + public boolean stop() { + return true; + } + + @Override + public long getUsedSpace(String path) { + File file = new File(path); + return file.getTotalSpace() - file.getFreeSpace(); + } + + @Override + public void setName(String name) { + // TODO Auto-generated method stub + + } + + @Override + public void setConfigParams(Map params) { + // TODO Auto-generated method stub + + } + + @Override + public Map getConfigParams() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getRunLevel() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void setRunLevel(int level) { + // TODO Auto-generated method stub + + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/storage/StorageLayer.java b/cosmic-core/nucleo/src/main/java/com/cloud/storage/StorageLayer.java new file mode 100644 index 0000000000..8421aeb128 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/storage/StorageLayer.java @@ -0,0 +1,155 @@ +// +// 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.storage; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import com.cloud.utils.component.Manager; + +/** + * StorageLayer is an independence layer for + * + * 1. Proper synchronization between threads. + * + * + */ +public interface StorageLayer extends Manager { + public final static String InstanceConfigKey = "storage.layer.instance"; + public final static String ClassConfigKey = "storage.layer.implementation"; + + /** + * @param path path to the file to get the size. + * @return size of the file. + */ + long getSize(String path); + + File createUniqDir(); + + /** + * Is this path a directory? + * @param path path to check. + * @return true if it is a directory; false otherwise. + */ + boolean isDirectory(String path); + + /** + * Is this path a file? + * @param path path to check. + * @return true if it is a file; false otherwise. + */ + boolean isFile(String path); + + /** + * creates the directory. All parent directories have to already exists. + * @param path path to make. + * @return true if created; false if not. + */ + boolean mkdir(String path); + + /** + * Creates the entire path. + * @param path path to create. + * @return true if created; false if not. + */ + boolean mkdirs(String path); + + /** + * Does this path exists? + * @param path directory or file to check if it exists. + * @return true if exists; false if not. + */ + boolean exists(String path); + + /** + * list all the files in a certain path. + * @param path directory that the file exists in. + * @return list of files that exists under this path. + */ + String[] listFiles(String path); + + /** + * Get the total disk size in bytes. + * @param path path + * @return disk size if path given is a disk; -1 if not. + */ + long getTotalSpace(String path); + + /** + * Get the total available disk size in bytes. + * @param path path to the disk. + * @return disk size if path given is a disk; -1 if not. + */ + long getUsedSpace(String path); + + /** + * Get the total available disk size in bytes. + * @param path path to the disk. + * @return disk size if path given is a disk; -1 if not. + */ + long getUsableSpace(String path); + + /** + * delete the path + * @param path to delete. + * @return true if deleted; false if not. + */ + boolean delete(String path); + + /** + * creates a file on this path. + * @param path directory to create the file in. + * @param filename file to create. + * @return true if created; false if not. + * @throws IOException if create has problems. + */ + boolean create(String path, String filename) throws IOException; + + /** + * clean up the path. This method will delete the parent paths if the parent + * paths do not contain children. If the original path cannot be deleted, + * this method returns false. If the parent cannot be deleted but does not + * have any children, this method throws IOException. + * @param path path to be cleaned up. + * @param rootPath delete up to this path. + * @return true if the path is deleted and false if it is not. + * @throws IOException if the parent has no children but delete failed. + */ + boolean cleanup(String path, String rootPath) throws IOException; + + /** + * Retrieves the actual file object. + * @param path path to the file. + * @return File object representing the file. + */ + File getFile(String path); + + /** + * Sets permissions for a file to be world readable and writeable + * @param file + * @return true if the file was set to be both world readable and writeable + */ + boolean setWorldReadableAndWriteable(File file); + + boolean deleteDir(String dir); + + List listMountPointsByMsHost(String path, long msHostId); +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/storage/resource/StoragePoolResource.java b/cosmic-core/nucleo/src/main/java/com/cloud/storage/resource/StoragePoolResource.java new file mode 100644 index 0000000000..a618e00234 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/storage/resource/StoragePoolResource.java @@ -0,0 +1,37 @@ +// +// 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.storage.resource; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.storage.CopyVolumeAnswer; +import com.cloud.agent.api.storage.CopyVolumeCommand; +import com.cloud.agent.api.storage.DestroyCommand; +import com.cloud.agent.api.storage.PrimaryStorageDownloadAnswer; +import com.cloud.agent.api.storage.PrimaryStorageDownloadCommand; + +public interface StoragePoolResource { + // FIXME: Should have a PrimaryStorageDownloadAnswer + PrimaryStorageDownloadAnswer execute(PrimaryStorageDownloadCommand cmd); + + // FIXME: Should have an DestroyAnswer + Answer execute(DestroyCommand cmd); + + CopyVolumeAnswer execute(CopyVolumeCommand cmd); +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/storage/resource/StorageProcessor.java b/cosmic-core/nucleo/src/main/java/com/cloud/storage/resource/StorageProcessor.java new file mode 100644 index 0000000000..f99ab6aacf --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/storage/resource/StorageProcessor.java @@ -0,0 +1,71 @@ +// +// 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.storage.resource; + +import com.cloud.agent.api.Answer; + +import org.apache.cloudstack.storage.command.AttachCommand; +import org.apache.cloudstack.storage.command.CopyCommand; +import org.apache.cloudstack.storage.command.CreateObjectCommand; +import org.apache.cloudstack.storage.command.DeleteCommand; +import org.apache.cloudstack.storage.command.DettachCommand; +import org.apache.cloudstack.storage.command.ForgetObjectCmd; +import org.apache.cloudstack.storage.command.IntroduceObjectCmd; +import org.apache.cloudstack.storage.command.SnapshotAndCopyCommand; + +public interface StorageProcessor { + public Answer copyTemplateToPrimaryStorage(CopyCommand cmd); + + public Answer cloneVolumeFromBaseTemplate(CopyCommand cmd); + + public Answer copyVolumeFromImageCacheToPrimary(CopyCommand cmd); + + public Answer copyVolumeFromPrimaryToSecondary(CopyCommand cmd); + + public Answer createTemplateFromVolume(CopyCommand cmd); + + public Answer createTemplateFromSnapshot(CopyCommand cmd); + + public Answer backupSnapshot(CopyCommand cmd); + + public Answer attachIso(AttachCommand cmd); + + public Answer attachVolume(AttachCommand cmd); + + public Answer dettachIso(DettachCommand cmd); + + public Answer dettachVolume(DettachCommand cmd); + + public Answer createVolume(CreateObjectCommand cmd); + + public Answer createSnapshot(CreateObjectCommand cmd); + + public Answer deleteVolume(DeleteCommand cmd); + + public Answer createVolumeFromSnapshot(CopyCommand cmd); + + public Answer deleteSnapshot(DeleteCommand cmd); + + public Answer introduceObject(IntroduceObjectCmd cmd); + + public Answer forgetObject(ForgetObjectCmd cmd); + + public Answer snapshotAndCopy(SnapshotAndCopyCommand cmd); +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/storage/resource/StorageSubsystemCommandHandler.java b/cosmic-core/nucleo/src/main/java/com/cloud/storage/resource/StorageSubsystemCommandHandler.java new file mode 100644 index 0000000000..bd2c09edc1 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/storage/resource/StorageSubsystemCommandHandler.java @@ -0,0 +1,28 @@ +// +// 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.storage.resource; + +import com.cloud.agent.api.Answer; + +import org.apache.cloudstack.storage.command.StorageSubSystemCommand; + +public interface StorageSubsystemCommandHandler { + public Answer handleStorageCommands(StorageSubSystemCommand command); +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/storage/resource/StorageSubsystemCommandHandlerBase.java b/cosmic-core/nucleo/src/main/java/com/cloud/storage/resource/StorageSubsystemCommandHandlerBase.java new file mode 100644 index 0000000000..71c864e701 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/storage/resource/StorageSubsystemCommandHandlerBase.java @@ -0,0 +1,155 @@ +// +// 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.storage.resource; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.to.DataObjectType; +import com.cloud.agent.api.to.DataStoreTO; +import com.cloud.agent.api.to.DataTO; +import com.cloud.agent.api.to.DiskTO; +import com.cloud.storage.DataStoreRole; +import com.cloud.storage.Volume; + +import org.apache.cloudstack.storage.command.AttachCommand; +import org.apache.cloudstack.storage.command.CopyCommand; +import org.apache.cloudstack.storage.command.CreateObjectAnswer; +import org.apache.cloudstack.storage.command.CreateObjectCommand; +import org.apache.cloudstack.storage.command.DeleteCommand; +import org.apache.cloudstack.storage.command.DettachCommand; +import org.apache.cloudstack.storage.command.IntroduceObjectCmd; +import org.apache.cloudstack.storage.command.SnapshotAndCopyCommand; +import org.apache.cloudstack.storage.command.StorageSubSystemCommand; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class StorageSubsystemCommandHandlerBase implements StorageSubsystemCommandHandler { + private static final Logger s_logger = LoggerFactory.getLogger(StorageSubsystemCommandHandlerBase.class); + protected StorageProcessor processor; + + public StorageSubsystemCommandHandlerBase(StorageProcessor processor) { + this.processor = processor; + } + + @Override + public Answer handleStorageCommands(StorageSubSystemCommand command) { + if (command instanceof CopyCommand) { + return this.execute((CopyCommand)command); + } else if (command instanceof CreateObjectCommand) { + return execute((CreateObjectCommand)command); + } else if (command instanceof DeleteCommand) { + return execute((DeleteCommand)command); + } else if (command instanceof AttachCommand) { + return execute((AttachCommand)command); + } else if (command instanceof DettachCommand) { + return execute((DettachCommand)command); + } else if (command instanceof IntroduceObjectCmd) { + return processor.introduceObject((IntroduceObjectCmd)command); + } else if (command instanceof SnapshotAndCopyCommand) { + return processor.snapshotAndCopy((SnapshotAndCopyCommand)command); + } + + return new Answer((Command)command, false, "not implemented yet"); + } + + protected Answer execute(CopyCommand cmd) { + DataTO srcData = cmd.getSrcTO(); + DataTO destData = cmd.getDestTO(); + DataStoreTO srcDataStore = srcData.getDataStore(); + DataStoreTO destDataStore = destData.getDataStore(); + + if (srcData.getObjectType() == DataObjectType.TEMPLATE && + (srcData.getDataStore().getRole() == DataStoreRole.Image || srcData.getDataStore().getRole() == DataStoreRole.ImageCache) && + destData.getDataStore().getRole() == DataStoreRole.Primary) { + //copy template to primary storage + return processor.copyTemplateToPrimaryStorage(cmd); + } else if (srcData.getObjectType() == DataObjectType.TEMPLATE && srcDataStore.getRole() == DataStoreRole.Primary && + destDataStore.getRole() == DataStoreRole.Primary) { + //clone template to a volume + return processor.cloneVolumeFromBaseTemplate(cmd); + } else if (srcData.getObjectType() == DataObjectType.VOLUME && + (srcData.getDataStore().getRole() == DataStoreRole.ImageCache || srcDataStore.getRole() == DataStoreRole.Image)) { + //copy volume from image cache to primary + return processor.copyVolumeFromImageCacheToPrimary(cmd); + } else if (srcData.getObjectType() == DataObjectType.VOLUME && srcData.getDataStore().getRole() == DataStoreRole.Primary) { + if (destData.getObjectType() == DataObjectType.VOLUME) { + return processor.copyVolumeFromPrimaryToSecondary(cmd); + } else if (destData.getObjectType() == DataObjectType.TEMPLATE) { + return processor.createTemplateFromVolume(cmd); + } + } else if (srcData.getObjectType() == DataObjectType.SNAPSHOT && destData.getObjectType() == DataObjectType.SNAPSHOT && + srcData.getDataStore().getRole() == DataStoreRole.Primary) { + return processor.backupSnapshot(cmd); + } else if (srcData.getObjectType() == DataObjectType.SNAPSHOT && destData.getObjectType() == DataObjectType.VOLUME) { + return processor.createVolumeFromSnapshot(cmd); + } else if (srcData.getObjectType() == DataObjectType.SNAPSHOT && destData.getObjectType() == DataObjectType.TEMPLATE) { + return processor.createTemplateFromSnapshot(cmd); + } + + return new Answer(cmd, false, "not implemented yet"); + } + + protected Answer execute(CreateObjectCommand cmd) { + DataTO data = cmd.getData(); + try { + if (data.getObjectType() == DataObjectType.VOLUME) { + return processor.createVolume(cmd); + } else if (data.getObjectType() == DataObjectType.SNAPSHOT) { + return processor.createSnapshot(cmd); + } + return new CreateObjectAnswer("not supported type"); + } catch (Exception e) { + s_logger.debug("Failed to create object: " + data.getObjectType() + ": " + e.toString()); + return new CreateObjectAnswer(e.toString()); + } + } + + protected Answer execute(DeleteCommand cmd) { + DataTO data = cmd.getData(); + Answer answer = null; + if (data.getObjectType() == DataObjectType.VOLUME) { + answer = processor.deleteVolume(cmd); + } else if (data.getObjectType() == DataObjectType.SNAPSHOT) { + answer = processor.deleteSnapshot(cmd); + } else { + answer = new Answer(cmd, false, "unsupported type"); + } + + return answer; + } + + protected Answer execute(AttachCommand cmd) { + DiskTO disk = cmd.getDisk(); + if (disk.getType() == Volume.Type.ISO) { + return processor.attachIso(cmd); + } else { + return processor.attachVolume(cmd); + } + } + + protected Answer execute(DettachCommand cmd) { + DiskTO disk = cmd.getDisk(); + if (disk.getType() == Volume.Type.ISO) { + return processor.dettachIso(cmd); + } else { + return processor.dettachVolume(cmd); + } + } +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/FtpTemplateUploader.java b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/FtpTemplateUploader.java new file mode 100644 index 0000000000..fee89e9d7f --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/FtpTemplateUploader.java @@ -0,0 +1,229 @@ +// +// 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.storage.template; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.Date; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class FtpTemplateUploader implements TemplateUploader { + + public static final Logger s_logger = LoggerFactory.getLogger(FtpTemplateUploader.class.getName()); + public TemplateUploader.Status status = TemplateUploader.Status.NOT_STARTED; + public String errorString = ""; + public long totalBytes = 0; + public long entitySizeinBytes; + private String sourcePath; + private String ftpUrl; + private UploadCompleteCallback completionCallback; + private BufferedInputStream inputStream = null; + private BufferedOutputStream outputStream = null; + private static final int CHUNK_SIZE = 1024 * 1024; //1M + + public FtpTemplateUploader(String sourcePath, String url, UploadCompleteCallback callback, long entitySizeinBytes) { + + this.sourcePath = sourcePath; + ftpUrl = url; + completionCallback = callback; + this.entitySizeinBytes = entitySizeinBytes; + + } + + @Override + public long upload(UploadCompleteCallback callback) { + + switch (status) { + case ABORTED: + case UNRECOVERABLE_ERROR: + case UPLOAD_FINISHED: + return 0; + default: + + } + + new Date(); + + StringBuffer sb = new StringBuffer(ftpUrl); + // check for authentication else assume its anonymous access. + /* if (user != null && password != null) + { + sb.append( user ); + sb.append( ':' ); + sb.append( password ); + sb.append( '@' ); + }*/ + /* + * type ==> a=ASCII mode, i=image (binary) mode, d= file directory + * listing + */ + sb.append(";type=i"); + + try { + URL url = new URL(sb.toString()); + URLConnection urlc = url.openConnection(); + File sourceFile = new File(sourcePath); + entitySizeinBytes = sourceFile.length(); + + outputStream = new BufferedOutputStream(urlc.getOutputStream()); + inputStream = new BufferedInputStream(new FileInputStream(sourceFile)); + + status = TemplateUploader.Status.IN_PROGRESS; + + int bytes = 0; + byte[] block = new byte[CHUNK_SIZE]; + boolean done = false; + while (!done && status != Status.ABORTED) { + if ((bytes = inputStream.read(block, 0, CHUNK_SIZE)) > -1) { + outputStream.write(block, 0, bytes); + totalBytes += bytes; + } else { + done = true; + } + } + status = TemplateUploader.Status.UPLOAD_FINISHED; + return totalBytes; + } catch (MalformedURLException e) { + status = TemplateUploader.Status.UNRECOVERABLE_ERROR; + errorString = e.getMessage(); + s_logger.error(errorString); + } catch (IOException e) { + status = TemplateUploader.Status.UNRECOVERABLE_ERROR; + errorString = e.getMessage(); + s_logger.error(errorString); + } finally { + try { + if (inputStream != null) { + inputStream.close(); + } + if (outputStream != null) { + outputStream.close(); + } + } catch (IOException ioe) { + s_logger.error(" Caught exception while closing the resources"); + } + if (callback != null) { + callback.uploadComplete(status); + } + } + + return 0; + } + + @Override + public void run() { + try { + upload(completionCallback); + } catch (Throwable t) { + s_logger.warn("Caught exception during upload " + t.getMessage(), t); + errorString = "Failed to install: " + t.getMessage(); + status = TemplateUploader.Status.UNRECOVERABLE_ERROR; + } + + } + + @Override + public Status getStatus() { + return status; + } + + @Override + public String getUploadError() { + return errorString; + } + + @Override + public String getUploadLocalPath() { + return sourcePath; + } + + @Override + public int getUploadPercent() { + if (entitySizeinBytes == 0) { + return 0; + } + return (int)(100.0 * totalBytes / entitySizeinBytes); + } + + @Override + public long getUploadTime() { + // TODO + return 0; + } + + @Override + public long getUploadedBytes() { + return totalBytes; + } + + @Override + public void setResume(boolean resume) { + + } + + @Override + public void setStatus(Status status) { + this.status = status; + } + + @Override + public void setUploadError(String string) { + errorString = string; + } + + @Override + public boolean stopUpload() { + switch (getStatus()) { + case IN_PROGRESS: + try { + if (outputStream != null) { + outputStream.close(); + } + if (inputStream != null) { + inputStream.close(); + } + } catch (IOException e) { + s_logger.error(" Caught exception while closing the resources"); + } + status = TemplateUploader.Status.ABORTED; + return true; + case UNKNOWN: + case NOT_STARTED: + case RECOVERABLE_ERROR: + case UNRECOVERABLE_ERROR: + case ABORTED: + status = TemplateUploader.Status.ABORTED; + case UPLOAD_FINISHED: + return true; + + default: + return true; + } + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java new file mode 100644 index 0000000000..f6d70fafbd --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java @@ -0,0 +1,439 @@ +// +// 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.storage.template; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Date; + +import com.cloud.storage.StorageLayer; +import com.cloud.utils.Pair; +import com.cloud.utils.UriUtils; +import com.cloud.utils.net.Proxy; + +import org.apache.cloudstack.managed.context.ManagedContextRunnable; +import org.apache.cloudstack.storage.command.DownloadCommand.ResourceType; +import org.apache.cloudstack.utils.imagestore.ImageStoreUtil; +import org.apache.commons.httpclient.Credentials; +import org.apache.commons.httpclient.Header; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpException; +import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.HttpMethodRetryHandler; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; +import org.apache.commons.httpclient.NoHttpResponseException; +import org.apache.commons.httpclient.UsernamePasswordCredentials; +import org.apache.commons.httpclient.auth.AuthScope; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.params.HttpMethodParams; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Download a template file using HTTP + * + */ +public class HttpTemplateDownloader extends ManagedContextRunnable implements TemplateDownloader { + public static final Logger s_logger = LoggerFactory.getLogger(HttpTemplateDownloader.class.getName()); + private static final MultiThreadedHttpConnectionManager s_httpClientManager = new MultiThreadedHttpConnectionManager(); + + private static final int CHUNK_SIZE = 1024 * 1024; //1M + private String downloadUrl; + private String toFile; + public TemplateDownloader.Status status = TemplateDownloader.Status.NOT_STARTED; + public String errorString = " "; + private long remoteSize = 0; + public long downloadTime = 0; + public long totalBytes; + private final HttpClient client; + private GetMethod request; + private boolean resume = false; + private DownloadCompleteCallback completionCallback; + StorageLayer _storage; + boolean inited = true; + + private String toDir; + private long maxTemplateSizeInBytes; + private ResourceType resourceType = ResourceType.TEMPLATE; + private final HttpMethodRetryHandler myretryhandler; + + public HttpTemplateDownloader(StorageLayer storageLayer, String downloadUrl, String toDir, DownloadCompleteCallback callback, long maxTemplateSizeInBytes, + String user, String password, Proxy proxy, ResourceType resourceType) { + _storage = storageLayer; + this.downloadUrl = downloadUrl; + setToDir(toDir); + status = TemplateDownloader.Status.NOT_STARTED; + this.resourceType = resourceType; + this.maxTemplateSizeInBytes = maxTemplateSizeInBytes; + + totalBytes = 0; + client = new HttpClient(s_httpClientManager); + + myretryhandler = new HttpMethodRetryHandler() { + @Override + public boolean retryMethod(final HttpMethod method, final IOException exception, int executionCount) { + if (executionCount >= 2) { + // Do not retry if over max retry count + return false; + } + if (exception instanceof NoHttpResponseException) { + // Retry if the server dropped connection on us + return true; + } + if (!method.isRequestSent()) { + // Retry if the request has not been sent fully or + // if it's OK to retry methods that have been sent + return true; + } + // otherwise do not retry + return false; + } + }; + + try { + request = new GetMethod(downloadUrl); + request.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, myretryhandler); + completionCallback = callback; + //this.request.setFollowRedirects(false); + + final File f = File.createTempFile("dnld", "tmp_", new File(toDir)); + + if (_storage != null) { + _storage.setWorldReadableAndWriteable(f); + } + + toFile = f.getAbsolutePath(); + final Pair hostAndPort = UriUtils.validateUrl(downloadUrl); + + if (proxy != null) { + client.getHostConfiguration().setProxy(proxy.getHost(), proxy.getPort()); + if (proxy.getUserName() != null) { + final Credentials proxyCreds = new UsernamePasswordCredentials(proxy.getUserName(), proxy.getPassword()); + client.getState().setProxyCredentials(AuthScope.ANY, proxyCreds); + } + } + if (user != null && password != null) { + client.getParams().setAuthenticationPreemptive(true); + final Credentials defaultcreds = new UsernamePasswordCredentials(user, password); + client.getState().setCredentials(new AuthScope(hostAndPort.first(), hostAndPort.second(), AuthScope.ANY_REALM), defaultcreds); + s_logger.info("Added username=" + user + ", password=" + password + "for host " + hostAndPort.first() + ":" + hostAndPort.second()); + } else { + s_logger.info("No credentials configured for host=" + hostAndPort.first() + ":" + hostAndPort.second()); + } + } catch (final IllegalArgumentException iae) { + errorString = iae.getMessage(); + status = TemplateDownloader.Status.UNRECOVERABLE_ERROR; + inited = false; + } catch (final Exception ex) { + errorString = "Unable to start download -- check url? "; + status = TemplateDownloader.Status.UNRECOVERABLE_ERROR; + s_logger.warn("Exception in constructor -- " + ex.toString()); + } catch (final Throwable th) { + s_logger.warn("throwable caught ", th); + } + } + + @Override + public long download(boolean resume, DownloadCompleteCallback callback) { + switch (status) { + case ABORTED: + case UNRECOVERABLE_ERROR: + case DOWNLOAD_FINISHED: + return 0; + default: + + } + int bytes = 0; + final File file = new File(toFile); + try { + + long localFileSize = 0; + if (file.exists() && resume) { + localFileSize = file.length(); + s_logger.info("Resuming download to file (current size)=" + localFileSize); + } + + final Date start = new Date(); + + int responseCode = 0; + + if (localFileSize > 0) { + // require partial content support for resume + request.addRequestHeader("Range", "bytes=" + localFileSize + "-"); + if (client.executeMethod(request) != HttpStatus.SC_PARTIAL_CONTENT) { + errorString = "HTTP Server does not support partial get"; + status = TemplateDownloader.Status.UNRECOVERABLE_ERROR; + return 0; + } + } else if ((responseCode = client.executeMethod(request)) != HttpStatus.SC_OK) { + status = TemplateDownloader.Status.UNRECOVERABLE_ERROR; + errorString = " HTTP Server returned " + responseCode + " (expected 200 OK) "; + return 0; //FIXME: retry? + } + + final Header contentLengthHeader = request.getResponseHeader("Content-Length"); + boolean chunked = false; + long remoteSize2 = 0; + if (contentLengthHeader == null) { + final Header chunkedHeader = request.getResponseHeader("Transfer-Encoding"); + if (chunkedHeader == null || !"chunked".equalsIgnoreCase(chunkedHeader.getValue())) { + status = TemplateDownloader.Status.UNRECOVERABLE_ERROR; + errorString = " Failed to receive length of download "; + return 0; //FIXME: what status do we put here? Do we retry? + } else if ("chunked".equalsIgnoreCase(chunkedHeader.getValue())) { + chunked = true; + } + } else { + remoteSize2 = Long.parseLong(contentLengthHeader.getValue()); + if (remoteSize2 == 0) { + status = TemplateDownloader.Status.DOWNLOAD_FINISHED; + final String downloaded = "(download complete remote=" + remoteSize + "bytes)"; + errorString = "Downloaded " + totalBytes + " bytes " + downloaded; + downloadTime = 0; + return 0; + } + } + + if (remoteSize == 0) { + remoteSize = remoteSize2; + } + + if (remoteSize > maxTemplateSizeInBytes) { + s_logger.info("Remote size is too large: " + remoteSize + " , max=" + maxTemplateSizeInBytes); + status = Status.UNRECOVERABLE_ERROR; + errorString = "Download file size is too large"; + return 0; + } + + if (remoteSize == 0) { + remoteSize = maxTemplateSizeInBytes; + } + + final URL url = new URL(getDownloadUrl()); + final InputStream in = url.openStream(); + + final RandomAccessFile out = new RandomAccessFile(file, "rw"); + out.seek(localFileSize); + + s_logger.info("Starting download from " + getDownloadUrl() + " to " + toFile + " remoteSize=" + remoteSize + " , max size=" + maxTemplateSizeInBytes); + + final byte[] block = new byte[CHUNK_SIZE]; + long offset = 0; + boolean done = false; + boolean verifiedFormat=false; + status = TemplateDownloader.Status.IN_PROGRESS; + while (!done && status != Status.ABORTED && offset <= remoteSize) { + if ((bytes = in.read(block, 0, CHUNK_SIZE)) > -1) { + out.write(block, 0, bytes); + offset += bytes; + out.seek(offset); + totalBytes += bytes; + if (!verifiedFormat && (offset >= 1048576 || offset >= remoteSize)) { //let's check format after we get 1MB or full file + String uripath = null; + try { + final URI str = new URI(getDownloadUrl()); + uripath = str.getPath(); + } catch (final URISyntaxException e) { + s_logger.warn("Invalid download url: " + getDownloadUrl() + ", This should not happen since we have validated the url before!!"); + } + final String unsupportedFormat = ImageStoreUtil.checkTemplateFormat(file.getAbsolutePath(), uripath); + if (unsupportedFormat == null || !unsupportedFormat.isEmpty()) { + try { + request.abort(); + out.close(); + in.close(); + } catch (final Exception ex) { + s_logger.debug("Error on http connection : " + ex.getMessage()); + } + status = Status.UNRECOVERABLE_ERROR; + errorString = "Template content is unsupported, or mismatch between selected format and template content. Found : " + unsupportedFormat; + return 0; + } + s_logger.debug("Verified format of downloading file " + file.getAbsolutePath() + " is supported"); + verifiedFormat = true; + } + } else { + done = true; + } + } + out.getFD().sync(); + + final Date finish = new Date(); + String downloaded = "(incomplete download)"; + if (totalBytes >= remoteSize) { + status = TemplateDownloader.Status.DOWNLOAD_FINISHED; + downloaded = "(download complete remote=" + remoteSize + "bytes)"; + } + errorString = "Downloaded " + totalBytes + " bytes " + downloaded; + downloadTime += finish.getTime() - start.getTime(); + in.close(); + out.close(); + + return totalBytes; + } catch (final HttpException hte) { + status = TemplateDownloader.Status.UNRECOVERABLE_ERROR; + errorString = hte.getMessage(); + } catch (final IOException ioe) { + status = TemplateDownloader.Status.UNRECOVERABLE_ERROR; //probably a file write error? + errorString = ioe.getMessage(); + } finally { + if (status == Status.UNRECOVERABLE_ERROR && file.exists() && !file.isDirectory()) { + file.delete(); + } + request.releaseConnection(); + if (callback != null) { + callback.downloadComplete(status); + } + } + return 0; + } + + public String getDownloadUrl() { + return downloadUrl; + } + + public String getToFile() { + final File file = new File(toFile); + + return file.getAbsolutePath(); + } + + @Override + public TemplateDownloader.Status getStatus() { + return status; + } + + @Override + public long getDownloadTime() { + return downloadTime; + } + + @Override + public long getDownloadedBytes() { + return totalBytes; + } + + @Override + @SuppressWarnings("fallthrough") + public boolean stopDownload() { + switch (getStatus()) { + case IN_PROGRESS: + if (request != null) { + request.abort(); + } + status = TemplateDownloader.Status.ABORTED; + return true; + case UNKNOWN: + case NOT_STARTED: + case RECOVERABLE_ERROR: + case UNRECOVERABLE_ERROR: + case ABORTED: + status = TemplateDownloader.Status.ABORTED; + case DOWNLOAD_FINISHED: + final File f = new File(toFile); + if (f.exists()) { + f.delete(); + } + return true; + + default: + return true; + } + } + + @Override + public int getDownloadPercent() { + if (remoteSize == 0) { + return 0; + } + + return (int)(100.0 * totalBytes / remoteSize); + } + + @Override + protected void runInContext() { + try { + download(resume, completionCallback); + } catch (final Throwable t) { + s_logger.warn("Caught exception during download " + t.getMessage(), t); + errorString = "Failed to install: " + t.getMessage(); + status = TemplateDownloader.Status.UNRECOVERABLE_ERROR; + } + + } + + @Override + public void setStatus(TemplateDownloader.Status status) { + this.status = status; + } + + public boolean isResume() { + return resume; + } + + @Override + public String getDownloadError() { + return errorString; + } + + @Override + public String getDownloadLocalPath() { + return getToFile(); + } + + @Override + public void setResume(boolean resume) { + this.resume = resume; + } + + public void setToDir(String toDir) { + this.toDir = toDir; + } + + public String getToDir() { + return toDir; + } + + @Override + public long getMaxTemplateSizeInBytes() { + return maxTemplateSizeInBytes; + } + + @Override + public void setDownloadError(String error) { + errorString = error; + } + + @Override + public boolean isInited() { + return inited; + } + + public ResourceType getResourceType() { + return resourceType; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/IsoProcessor.java b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/IsoProcessor.java new file mode 100644 index 0000000000..f3beba46b4 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/IsoProcessor.java @@ -0,0 +1,75 @@ +// +// 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.storage.template; + +import java.io.File; +import java.util.Map; + +import javax.naming.ConfigurationException; + +import com.cloud.storage.Storage.ImageFormat; +import com.cloud.storage.StorageLayer; +import com.cloud.utils.component.AdapterBase; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class IsoProcessor extends AdapterBase implements Processor { + private static final Logger s_logger = LoggerFactory.getLogger(IsoProcessor.class); + + StorageLayer _storage; + + @Override + public FormatInfo process(String templatePath, ImageFormat format, String templateName) { + if (format != null) { + s_logger.debug("We don't handle conversion from " + format + " to ISO."); + return null; + } + + String isoPath = templatePath + File.separator + templateName + "." + ImageFormat.ISO.getFileExtension(); + + if (!_storage.exists(isoPath)) { + s_logger.debug("Unable to find the iso file: " + isoPath); + return null; + } + + FormatInfo info = new FormatInfo(); + info.format = ImageFormat.ISO; + info.filename = templateName + "." + ImageFormat.ISO.getFileExtension(); + info.size = _storage.getSize(isoPath); + info.virtualSize = info.size; + + return info; + } + + @Override + public long getVirtualSize(File file) { + return file.length(); + } + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + _storage = (StorageLayer)params.get(StorageLayer.InstanceConfigKey); + if (_storage == null) { + throw new ConfigurationException("Unable to get storage implementation"); + } + return true; + } +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/LocalTemplateDownloader.java b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/LocalTemplateDownloader.java new file mode 100644 index 0000000000..fe2ceced54 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/LocalTemplateDownloader.java @@ -0,0 +1,184 @@ +// +// 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.storage.template; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; + +import com.cloud.storage.StorageLayer; +import com.cloud.utils.exception.CloudRuntimeException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LocalTemplateDownloader extends TemplateDownloaderBase implements TemplateDownloader { + public static final Logger s_logger = LoggerFactory.getLogger(LocalTemplateDownloader.class); + + public LocalTemplateDownloader(StorageLayer storageLayer, String downloadUrl, String toDir, + long maxTemplateSizeInBytes, DownloadCompleteCallback callback) { + super(storageLayer, downloadUrl, toDir, maxTemplateSizeInBytes, callback); + final String filename = new File(downloadUrl).getName(); + _toFile = toDir.endsWith(File.separator) ? toDir + filename : toDir + File.separator + filename; + } + + public LocalTemplateDownloader(StorageLayer storageLayer, String downloadUrl, String toDir, + long maxTemplateSizeInBytes) { + super(storageLayer, downloadUrl, toDir, maxTemplateSizeInBytes, null); + final String filename = new File(downloadUrl).getName(); + _toFile = toDir.endsWith(File.separator) ? toDir + filename : toDir + File.separator + filename; + } + + public LocalTemplateDownloader(String downloadUrl, String toDir, long maxTemplateSizeInBytes) { + super(null, downloadUrl, toDir, maxTemplateSizeInBytes, null); + final String filename = new File(downloadUrl).getName(); + _toFile = toDir.endsWith(File.separator) ? toDir + filename : toDir + File.separator + filename; + } + + @Override + public long download(boolean resume, DownloadCompleteCallback callback) { + if (_status == Status.ABORTED || _status == Status.UNRECOVERABLE_ERROR || _status == Status.DOWNLOAD_FINISHED) { + throw new CloudRuntimeException("Invalid status for downloading: " + _status); + } + + _start = System.currentTimeMillis(); + _resume = resume; + + File src; + try { + src = new File(new URI(_downloadUrl)); + } catch (final URISyntaxException e) { + final String message = "Invalid URI " + _downloadUrl; + s_logger.warn(message); + _status = Status.UNRECOVERABLE_ERROR; + throw new CloudRuntimeException(message, e); + } + + final File dst = new File(_toFile); + + FileChannel fic = null; + FileChannel foc = null; + FileInputStream fis = null; + FileOutputStream fos = null; + + try { + if (_storage != null) { + dst.createNewFile(); + _storage.setWorldReadableAndWriteable(dst); + } + + final ByteBuffer buffer = ByteBuffer.allocate(1024 * 512); + + try { + fis = new FileInputStream(src); + } catch (final FileNotFoundException e) { + _errorString = "Unable to find " + _downloadUrl; + s_logger.warn(_errorString); + throw new CloudRuntimeException(_errorString, e); + } + fic = fis.getChannel(); + try { + if (!dst.exists()) { + dst.delete(); + } + fos = new FileOutputStream(dst); + } catch (final FileNotFoundException e) { + final String message = "Unable to find " + _toFile; + s_logger.warn(message); + throw new CloudRuntimeException(message, e); + } + foc = fos.getChannel(); + + _remoteSize = src.length(); + _totalBytes = 0; + _status = TemplateDownloader.Status.IN_PROGRESS; + + try { + while (_status != Status.ABORTED && fic.read(buffer) != -1) { + buffer.flip(); + final int count = foc.write(buffer); + _totalBytes += count; + buffer.clear(); + } + } catch (final IOException e) { + s_logger.warn("Unable to download"); + } + + String downloaded = "(incomplete download)"; + if (_totalBytes == _remoteSize) { + _status = TemplateDownloader.Status.DOWNLOAD_FINISHED; + downloaded = "(download complete)"; + } + + _errorString = "Downloaded " + _remoteSize + " bytes " + downloaded; + _downloadTime += System.currentTimeMillis() - _start; + return _totalBytes; + } catch (final Exception e) { + _status = TemplateDownloader.Status.UNRECOVERABLE_ERROR; + _errorString = e.getMessage(); + throw new CloudRuntimeException(_errorString, e); + } finally { + if (fic != null) { + try { + fic.close(); + } catch (final IOException e) { + s_logger.info("[ignore] error while closing file input channel."); + } + } + + if (foc != null) { + try { + foc.close(); + } catch (final IOException e) { + s_logger.info("[ignore] error while closing file output channel."); + } + } + + if (fis != null) { + try { + fis.close(); + } catch (final IOException e) { + s_logger.info("[ignore] error while closing file input stream."); + } + } + + if (fos != null) { + try { + fos.close(); + } catch (final IOException e) { + s_logger.info("[ignore] error while closing file output stream."); + } + } + + if (_status == Status.UNRECOVERABLE_ERROR && dst.exists()) { + dst.delete(); + } + if (callback != null) { + callback.downloadComplete(_status); + } + } + } +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/Processor.java b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/Processor.java new file mode 100644 index 0000000000..ba57563e1b --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/Processor.java @@ -0,0 +1,57 @@ +// +// 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.storage.template; + +import java.io.File; +import java.io.IOException; + +import com.cloud.exception.InternalErrorException; +import com.cloud.storage.Storage.ImageFormat; +import com.cloud.utils.component.Adapter; + +/** + * Generic interface to process different types of image formats + * for templates downloaded and for conversion from one format + * to anther. + * + */ +public interface Processor extends Adapter { + + /** + * Returns image format if it was able to process the original file and + * + * @param templatePath path to the templates to process. + * @param format Format of the original file. If null, it means unknown. If not null, + * there is already a file with thte template name and image format extension + * that exists in case a conversion can be done. + */ + FormatInfo process(String templatePath, ImageFormat format, String templateName) throws InternalErrorException; + + public static class FormatInfo { + public ImageFormat format; + public long size; + public long virtualSize; + public String filename; + public boolean isCorrupted; + } + + long getVirtualSize(File file) throws IOException; + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/QCOW2Processor.java b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/QCOW2Processor.java new file mode 100644 index 0000000000..38d588225b --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/QCOW2Processor.java @@ -0,0 +1,110 @@ +// +// 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.storage.template; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Map; + +import javax.naming.ConfigurationException; + +import com.cloud.exception.InternalErrorException; +import com.cloud.storage.Storage.ImageFormat; +import com.cloud.storage.StorageLayer; +import com.cloud.utils.NumbersUtil; +import com.cloud.utils.component.AdapterBase; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class QCOW2Processor extends AdapterBase implements Processor { + private static final Logger s_logger = LoggerFactory.getLogger(QCOW2Processor.class); + private static final int VIRTUALSIZE_HEADER_LOCATION = 24; + + private StorageLayer _storage; + + @Override + public FormatInfo process(String templatePath, ImageFormat format, String templateName) throws InternalErrorException { + if (format != null) { + s_logger.debug("We currently don't handle conversion from " + format + " to QCOW2."); + return null; + } + + String qcow2Path = templatePath + File.separator + templateName + "." + ImageFormat.QCOW2.getFileExtension(); + + if (!_storage.exists(qcow2Path)) { + s_logger.debug("Unable to find the qcow2 file: " + qcow2Path); + return null; + } + + FormatInfo info = new FormatInfo(); + info.format = ImageFormat.QCOW2; + info.filename = templateName + "." + ImageFormat.QCOW2.getFileExtension(); + + File qcow2File = _storage.getFile(qcow2Path); + + info.size = _storage.getSize(qcow2Path); + + try { + info.virtualSize = getTemplateVirtualSize(qcow2File); + } catch (IOException e) { + s_logger.error("Unable to get virtual size from " + qcow2File.getName()); + throw new InternalErrorException("unable to get virtual size from qcow2 file"); + } + + return info; + } + + @Override + public long getVirtualSize(File file) throws IOException { + try { + long size = getTemplateVirtualSize(file); + return size; + } catch (Exception e) { + s_logger.info("[ignored]" + "failed to get template virtual size for QCOW2: " + e.getLocalizedMessage()); + } + return file.length(); + } + + protected long getTemplateVirtualSize(File file) throws IOException { + byte[] b = new byte[8]; + try (FileInputStream strm = new FileInputStream(file)) { + if (strm.skip(VIRTUALSIZE_HEADER_LOCATION) != VIRTUALSIZE_HEADER_LOCATION) { + throw new IOException("Unable to skip to the virtual size header"); + } + if (strm.read(b) != 8) { + throw new IOException("Unable to properly read the size"); + } + } + + return NumbersUtil.bytesToLong(b); + } + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + _storage = (StorageLayer)params.get(StorageLayer.InstanceConfigKey); + if (_storage == null) { + throw new ConfigurationException("Unable to get storage implementation"); + } + + return true; + } +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/RawImageProcessor.java b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/RawImageProcessor.java new file mode 100644 index 0000000000..a4cbe01b49 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/RawImageProcessor.java @@ -0,0 +1,75 @@ +// +// 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.storage.template; + +import java.io.File; +import java.util.Map; + +import javax.naming.ConfigurationException; + +import com.cloud.exception.InternalErrorException; +import com.cloud.storage.Storage.ImageFormat; +import com.cloud.storage.StorageLayer; +import com.cloud.utils.component.AdapterBase; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RawImageProcessor extends AdapterBase implements Processor { + private static final Logger s_logger = LoggerFactory.getLogger(RawImageProcessor.class); + StorageLayer _storage; + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + _storage = (StorageLayer)params.get(StorageLayer.InstanceConfigKey); + if (_storage == null) { + throw new ConfigurationException("Unable to get storage implementation"); + } + + return true; + } + + @Override + public FormatInfo process(String templatePath, ImageFormat format, String templateName) throws InternalErrorException { + if (format != null) { + s_logger.debug("We currently don't handle conversion from " + format + " to raw image."); + return null; + } + + String imgPath = templatePath + File.separator + templateName + "." + ImageFormat.RAW.getFileExtension(); + if (!_storage.exists(imgPath)) { + s_logger.debug("Unable to find raw image:" + imgPath); + return null; + } + FormatInfo info = new FormatInfo(); + info.format = ImageFormat.RAW; + info.filename = templateName + "." + ImageFormat.RAW.getFileExtension(); + info.size = _storage.getSize(imgPath); + info.virtualSize = info.size; + s_logger.debug("Process raw image " + info.filename + " successfully"); + return info; + } + + @Override + public long getVirtualSize(File file) { + return file.length(); + } + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/S3TemplateDownloader.java b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/S3TemplateDownloader.java new file mode 100644 index 0000000000..651f37299b --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/S3TemplateDownloader.java @@ -0,0 +1,379 @@ +// +// 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.storage.template; + +import static java.util.Arrays.asList; + +import static com.cloud.utils.StringUtils.join; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Date; + +import com.amazonaws.event.ProgressEvent; +import com.amazonaws.event.ProgressEventType; +import com.amazonaws.event.ProgressListener; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectRequest; +import com.amazonaws.services.s3.model.StorageClass; +import com.amazonaws.services.s3.transfer.Upload; +import com.cloud.agent.api.to.S3TO; +import com.cloud.utils.net.HTTPUtils; +import com.cloud.utils.net.Proxy; +import com.cloud.utils.storage.S3.S3Utils; + +import org.apache.cloudstack.managed.context.ManagedContextRunnable; +import org.apache.cloudstack.storage.command.DownloadCommand.ResourceType; +import org.apache.commons.httpclient.Header; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.URIException; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.params.HttpMethodParams; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Download a template file using HTTP(S) + * + * This class, once instantiated, has the purpose to download a single Template to an S3 Image Store. + * + * Execution of the instance is started when runInContext() is called. + */ +public class S3TemplateDownloader extends ManagedContextRunnable implements TemplateDownloader { + private static final Logger LOGGER = LoggerFactory.getLogger(S3TemplateDownloader.class.getName()); + + private final String downloadUrl; + private final String s3Key; + private final String fileExtension; + private final HttpClient httpClient; + private final GetMethod getMethod; + private final DownloadCompleteCallback downloadCompleteCallback; + private final S3TO s3TO; + private String errorString = ""; + private TemplateDownloader.Status status = TemplateDownloader.Status.NOT_STARTED; + private ResourceType resourceType = ResourceType.TEMPLATE; + private long remoteSize; + private long downloadTime; + private long totalBytes; + private long maxTemplateSizeInByte; + + private boolean resume = false; + + public S3TemplateDownloader(S3TO s3TO, String downloadUrl, String installPath, DownloadCompleteCallback downloadCompleteCallback, + long maxTemplateSizeInBytes, String username, String password, Proxy proxy, ResourceType resourceType) { + this.downloadUrl = downloadUrl; + this.s3TO = s3TO; + this.resourceType = resourceType; + this.maxTemplateSizeInByte = maxTemplateSizeInBytes; + this.httpClient = HTTPUtils.getHTTPClient(); + this.downloadCompleteCallback = downloadCompleteCallback; + + // Create a GET method for the download url. + this.getMethod = new GetMethod(downloadUrl); + + // Set the retry handler, default to retry 5 times. + this.getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, HTTPUtils.getHttpMethodRetryHandler(5)); + + // Follow redirects + this.getMethod.setFollowRedirects(true); + + // Set file extension. + this.fileExtension = StringUtils.substringAfterLast(StringUtils.substringAfterLast(downloadUrl, "/"), "."); + + // Calculate and set S3 Key. + this.s3Key = join(asList(installPath, StringUtils.substringAfterLast(downloadUrl, "/")), S3Utils.SEPARATOR); + + // Set proxy if available. + HTTPUtils.setProxy(proxy, this.httpClient); + + // Set credentials if available. + HTTPUtils.setCredentials(username, password, this.httpClient); + } + + @Override + public long download(boolean resume, DownloadCompleteCallback callback) { + if (!status.equals(Status.NOT_STARTED)) { + // Only start downloading if we haven't started yet. + LOGGER.debug("Template download is already started, not starting again. Template: " + downloadUrl); + + return 0; + } + + int responseCode; + if ((responseCode = HTTPUtils.executeMethod(httpClient, getMethod)) == -1) { + errorString = "Exception while executing HttpMethod " + getMethod.getName() + " on URL " + downloadUrl; + LOGGER.warn(errorString); + + status = Status.UNRECOVERABLE_ERROR; + return 0; + } + + if (!HTTPUtils.verifyResponseCode(responseCode)) { + errorString = "Response code for GetMethod of " + downloadUrl + " is incorrect, responseCode: " + responseCode; + LOGGER.warn(errorString); + + status = Status.UNRECOVERABLE_ERROR; + return 0; + } + + // Headers + Header contentLengthHeader = getMethod.getResponseHeader("Content-Length"); + Header contentTypeHeader = getMethod.getResponseHeader("Content-Type"); + + // Check the contentLengthHeader and transferEncodingHeader. + if (contentLengthHeader == null) { + errorString = "The ContentLengthHeader of " + downloadUrl + " isn't supplied"; + LOGGER.warn(errorString); + + status = Status.UNRECOVERABLE_ERROR; + return 0; + } else { + // The ContentLengthHeader is supplied, parse it's value. + remoteSize = Long.parseLong(contentLengthHeader.getValue()); + } + + if (remoteSize > maxTemplateSizeInByte) { + errorString = "Remote size is too large for template " + downloadUrl + " remote size is " + remoteSize + " max allowed is " + maxTemplateSizeInByte; + LOGGER.warn(errorString); + + status = Status.UNRECOVERABLE_ERROR; + return 0; + } + + InputStream inputStream; + + try { + inputStream = new BufferedInputStream(getMethod.getResponseBodyAsStream()); + } catch (IOException e) { + errorString = "Exception occurred while opening InputStream for template " + downloadUrl; + LOGGER.warn(errorString); + + status = Status.UNRECOVERABLE_ERROR; + return 0; + } + + LOGGER.info("Starting download from " + downloadUrl + " to S3 bucket " + s3TO.getBucketName() + " and size " + remoteSize + " bytes"); + + // Time the upload starts. + final Date start = new Date(); + + ObjectMetadata objectMetadata = new ObjectMetadata(); + objectMetadata.setContentLength(remoteSize); + + if (contentTypeHeader.getValue() != null) { + objectMetadata.setContentType(contentTypeHeader.getValue()); + } + + // Create the PutObjectRequest. + PutObjectRequest putObjectRequest = new PutObjectRequest(s3TO.getBucketName(), s3Key, inputStream, objectMetadata); + + // If reduced redundancy is enabled, set it. + if (s3TO.getEnableRRS()) { + putObjectRequest.withStorageClass(StorageClass.ReducedRedundancy); + } + + Upload upload = S3Utils.putObject(s3TO, putObjectRequest); + + upload.addProgressListener(new ProgressListener() { + @Override + public void progressChanged(ProgressEvent progressEvent) { + + // Record the amount of bytes transferred. + totalBytes += progressEvent.getBytesTransferred(); + + LOGGER.trace("Template download from " + downloadUrl + " to S3 bucket " + s3TO.getBucketName() + " transferred " + totalBytes + " in " + ((new Date().getTime() - start.getTime()) / 1000) + " seconds"); + + if (progressEvent.getEventType() == ProgressEventType.TRANSFER_STARTED_EVENT) { + status = Status.IN_PROGRESS; + } else if (progressEvent.getEventType() == ProgressEventType.TRANSFER_COMPLETED_EVENT) { + status = Status.DOWNLOAD_FINISHED; + } else if (progressEvent.getEventType() == ProgressEventType.TRANSFER_CANCELED_EVENT) { + status = Status.ABORTED; + } else if (progressEvent.getEventType() == ProgressEventType.TRANSFER_FAILED_EVENT) { + status = Status.UNRECOVERABLE_ERROR; + } + } + }); + + try { + // Wait for the upload to complete. + upload.waitForCompletion(); + } catch (InterruptedException e) { + // Interruption while waiting for the upload to complete. + LOGGER.warn("Interruption occurred while waiting for upload of " + downloadUrl + " to complete"); + } + + downloadTime = new Date().getTime() - start.getTime(); + + if (status == Status.DOWNLOAD_FINISHED) { + LOGGER.info("Template download from " + downloadUrl + " to S3 bucket " + s3TO.getBucketName() + " transferred " + totalBytes + " in " + (downloadTime / 1000) + " seconds, completed successfully!"); + } else { + LOGGER.warn("Template download from " + downloadUrl + " to S3 bucket " + s3TO.getBucketName() + " transferred " + totalBytes + " in " + (downloadTime / 1000) + " seconds, completed with status " + status.toString()); + } + + // Close input stream + getMethod.releaseConnection(); + + // Call the callback! + if (callback != null) { + callback.downloadComplete(status); + } + + return totalBytes; + } + + public String getDownloadUrl() { + try { + return getMethod.getURI().toString(); + } catch (URIException e) { + return null; + } + } + + @Override + public Status getStatus() { + return status; + } + + @Override + public long getDownloadTime() { + return downloadTime; + } + + @Override + public long getDownloadedBytes() { + return totalBytes; + } + + /** + * Returns an InputStream only when the status is DOWNLOAD_FINISHED. + * + * The caller of this method must close the InputStream to prevent resource leaks! + * + * @return S3ObjectInputStream of the object. + */ + public InputStream getS3ObjectInputStream() { + // Check if the download is finished + if (status != Status.DOWNLOAD_FINISHED) { + return null; + } + + return S3Utils.getObjectStream(s3TO, s3TO.getBucketName(), s3Key); + } + + public void cleanupAfterError() { + LOGGER.warn("Cleanup after error, trying to remove object: " + s3Key); + + S3Utils.deleteObject(s3TO, s3TO.getBucketName(), s3Key); + } + + @Override + public boolean stopDownload() { + switch (status) { + case IN_PROGRESS: + if (getMethod != null) { + getMethod.abort(); + } + break; + case UNKNOWN: + case NOT_STARTED: + case RECOVERABLE_ERROR: + case UNRECOVERABLE_ERROR: + case ABORTED: + case DOWNLOAD_FINISHED: + // Remove the object if it already has been uploaded. + S3Utils.deleteObject(s3TO, s3TO.getBucketName(), s3Key); + break; + default: + break; + } + + status = TemplateDownloader.Status.ABORTED; + return true; + } + + @Override + public int getDownloadPercent() { + if (remoteSize == 0) { + return 0; + } + + return (int) (100.0 * totalBytes / remoteSize); + } + + @Override + protected void runInContext() { + // Start the download! + download(resume, downloadCompleteCallback); + } + + @Override + public void setStatus(Status status) { + this.status = status; + } + + public boolean isResume() { + return resume; + } + + @Override + public String getDownloadError() { + return errorString; + } + + @Override + public String getDownloadLocalPath() { + return s3Key; + } + + @Override + public void setResume(boolean resume) { + this.resume = resume; + } + + @Override + public long getMaxTemplateSizeInBytes() { + return maxTemplateSizeInByte; + } + + @Override + public void setDownloadError(String error) { + errorString = error; + } + + @Override + public boolean isInited() { + return true; + } + + public ResourceType getResourceType() { + return resourceType; + } + + public long getTotalBytes() { + return totalBytes; + } + + public String getFileExtension() { + return fileExtension; + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/ScpTemplateDownloader.java b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/ScpTemplateDownloader.java new file mode 100644 index 0000000000..aaa040a573 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/ScpTemplateDownloader.java @@ -0,0 +1,151 @@ +// +// 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.storage.template; + +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; + +import com.cloud.storage.StorageLayer; +import com.cloud.utils.exception.CloudRuntimeException; +import com.trilead.ssh2.SCPClient; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ScpTemplateDownloader extends TemplateDownloaderBase implements TemplateDownloader { + private static final Logger s_logger = LoggerFactory.getLogger(ScpTemplateDownloader.class); + + public ScpTemplateDownloader(StorageLayer storageLayer, String downloadUrl, String toDir, long maxTemplateSizeInBytes, DownloadCompleteCallback callback) { + super(storageLayer, downloadUrl, toDir, maxTemplateSizeInBytes, callback); + + URI uri; + try { + uri = new URI(_downloadUrl); + } catch (final URISyntaxException e) { + s_logger.warn("URI syntax error: " + _downloadUrl); + _status = Status.UNRECOVERABLE_ERROR; + return; + } + + final String path = uri.getPath(); + final String filename = path.substring(path.lastIndexOf("/") + 1); + _toFile = toDir + File.separator + filename; + } + + @Override + public long download(boolean resume, DownloadCompleteCallback callback) { + if (_status == Status.ABORTED || _status == Status.UNRECOVERABLE_ERROR || _status == Status.DOWNLOAD_FINISHED) { + return 0; + } + + _resume = resume; + + _start = System.currentTimeMillis(); + + URI uri; + try { + uri = new URI(_downloadUrl); + } catch (final URISyntaxException e1) { + _status = Status.UNRECOVERABLE_ERROR; + return 0; + } + + final String username = uri.getUserInfo(); + final String queries = uri.getQuery(); + String password = null; + if (queries != null) { + final String[] qs = queries.split("&"); + for (final String q : qs) { + final String[] tokens = q.split("="); + if (tokens[0].equalsIgnoreCase("password")) { + password = tokens[1]; + break; + } + } + } + int port = uri.getPort(); + if (port == -1) { + port = 22; + } + final File file = new File(_toFile); + + final com.trilead.ssh2.Connection sshConnection = new com.trilead.ssh2.Connection(uri.getHost(), port); + try { + if (_storage != null) { + file.createNewFile(); + _storage.setWorldReadableAndWriteable(file); + } + + sshConnection.connect(null, 60000, 60000); + if (!sshConnection.authenticateWithPassword(username, password)) { + throw new CloudRuntimeException("Unable to authenticate"); + } + + final SCPClient scp = new SCPClient(sshConnection); + + final String src = uri.getPath(); + + _status = Status.IN_PROGRESS; + scp.get(src, _toDir); + + if (!file.exists()) { + _status = Status.UNRECOVERABLE_ERROR; + s_logger.debug("unable to scp the file " + _downloadUrl); + return 0; + } + + _status = Status.DOWNLOAD_FINISHED; + + _totalBytes = file.length(); + + final String downloaded = "(download complete)"; + + _errorString = "Downloaded " + _remoteSize + " bytes " + downloaded; + _downloadTime += System.currentTimeMillis() - _start; + return _totalBytes; + + } catch (final Exception e) { + s_logger.warn("Unable to download " + _downloadUrl, e); + _status = TemplateDownloader.Status.UNRECOVERABLE_ERROR; + _errorString = e.getMessage(); + return 0; + } finally { + sshConnection.close(); + if (_status == Status.UNRECOVERABLE_ERROR && file.exists()) { + file.delete(); + } + if (callback != null) { + callback.downloadComplete(_status); + } + } + } + + @Override + public int getDownloadPercent() { + if (_status == Status.DOWNLOAD_FINISHED) { + return 100; + } else if (_status == Status.IN_PROGRESS) { + return 50; + } else { + return 0; + } + } +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/TARProcessor.java b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/TARProcessor.java new file mode 100644 index 0000000000..90f35deea5 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/TARProcessor.java @@ -0,0 +1,80 @@ +// +// 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.storage.template; + +import java.io.File; +import java.util.Map; + +import javax.naming.ConfigurationException; + +import com.cloud.storage.Storage.ImageFormat; +import com.cloud.storage.StorageLayer; +import com.cloud.utils.component.AdapterBase; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TARProcessor extends AdapterBase implements Processor { + private static final Logger s_logger = LoggerFactory.getLogger(TARProcessor.class); + + private StorageLayer _storage; + + @Override + public FormatInfo process(String templatePath, ImageFormat format, String templateName) { + if (format != null) { + s_logger.debug("We currently don't handle conversion from " + format + " to TAR."); + return null; + } + + String tarPath = templatePath + File.separator + templateName + "." + ImageFormat.TAR.getFileExtension(); + + if (!_storage.exists(tarPath)) { + s_logger.debug("Unable to find the tar file: " + tarPath); + return null; + } + + FormatInfo info = new FormatInfo(); + info.format = ImageFormat.TAR; + info.filename = templateName + "." + ImageFormat.TAR.getFileExtension(); + + File tarFile = _storage.getFile(tarPath); + + info.size = _storage.getSize(tarPath); + + info.virtualSize = getVirtualSize(tarFile); + + return info; + } + + @Override + public long getVirtualSize(File file) { + return file.length(); + } + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + _storage = (StorageLayer)params.get(StorageLayer.InstanceConfigKey); + if (_storage == null) { + throw new ConfigurationException("Unable to get storage implementation"); + } + + return true; + } +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/TemplateConstants.java b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/TemplateConstants.java new file mode 100644 index 0000000000..25c2d5b3c0 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/TemplateConstants.java @@ -0,0 +1,38 @@ +// +// 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.storage.template; + +public final class TemplateConstants { + public static final String DEFAULT_TMPLT_ROOT_DIR = "template"; + public static final String DEFAULT_SNAPSHOT_ROOT_DIR = "snapshots"; + public static final String DEFAULT_VOLUME_ROOT_DIR = "volumes"; + public static final String DEFAULT_TMPLT_FIRST_LEVEL_DIR = "tmpl/"; + + public static final String DEFAULT_SYSTEM_VM_TEMPLATE_PATH = "template/tmpl/1/"; + + public static final String DEFAULT_SYSTEM_VM_TMPLT_NAME = "routing"; + + public static final int DEFAULT_TMPLT_COPY_PORT = 80; + public static final String DEFAULT_TMPLT_COPY_INTF = "eth2"; + + public static final String DEFAULT_SSL_CERT_DOMAIN = "realhostip.com"; + public static final String DEFAULT_HTTP_AUTH_USER = "cloud"; + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/TemplateDownloader.java b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/TemplateDownloader.java new file mode 100644 index 0000000000..5db3d2425a --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/TemplateDownloader.java @@ -0,0 +1,95 @@ +// +// 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.storage.template; + +public interface TemplateDownloader extends Runnable { + + /** + * Callback used to notify completion of download + * + */ + interface DownloadCompleteCallback { + void downloadComplete(Status status); + + } + + enum Status { + UNKNOWN, NOT_STARTED, IN_PROGRESS, ABORTED, UNRECOVERABLE_ERROR, RECOVERABLE_ERROR, DOWNLOAD_FINISHED, POST_DOWNLOAD_FINISHED + } + + long DEFAULT_MAX_TEMPLATE_SIZE_IN_BYTES = 50L * 1024L * 1024L * 1024L; + + /** + * Initiate download, resuming a previous one if required + * @param resume resume if necessary + * @param callback completion callback to be called after download is complete + * @return bytes downloaded + */ + long download(boolean resume, DownloadCompleteCallback callback); + + /** + * @return + */ + boolean stopDownload(); + + /** + * @return percent of file downloaded + */ + int getDownloadPercent(); + + /** + * Get the status of the download + * @return status of download + */ + TemplateDownloader.Status getStatus(); + + /** + * Get time taken to download so far + * @return time in seconds taken to download + */ + long getDownloadTime(); + + /** + * Get bytes downloaded + * @return bytes downloaded so far + */ + long getDownloadedBytes(); + + /** + * Get the error if any + * @return error string if any + */ + String getDownloadError(); + + /** Get local path of the downloaded file + * @return local path of the file downloaded + */ + String getDownloadLocalPath(); + + void setStatus(TemplateDownloader.Status status); + + void setDownloadError(String string); + + void setResume(boolean resume); + + boolean isInited(); + + long getMaxTemplateSizeInBytes(); +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/TemplateDownloaderBase.java b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/TemplateDownloaderBase.java new file mode 100644 index 0000000000..525532630a --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/TemplateDownloaderBase.java @@ -0,0 +1,152 @@ +// +// 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.storage.template; + +import java.io.File; + +import com.cloud.storage.StorageLayer; + +import org.apache.cloudstack.managed.context.ManagedContextRunnable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class TemplateDownloaderBase extends ManagedContextRunnable implements TemplateDownloader { + private static final Logger s_logger = LoggerFactory.getLogger(TemplateDownloaderBase.class); + + protected String _downloadUrl; + protected String _toFile; + protected TemplateDownloader.Status _status = TemplateDownloader.Status.NOT_STARTED; + protected String _errorString = " "; + protected long _remoteSize = 0; + protected long _downloadTime = 0; + protected long _totalBytes; + protected DownloadCompleteCallback _callback; + protected boolean _resume = false; + protected String _toDir; + protected long _start; + protected StorageLayer _storage; + protected boolean _inited = false; + private long maxTemplateSizeInBytes; + + public TemplateDownloaderBase(StorageLayer storage, String downloadUrl, String toDir, long maxTemplateSizeInBytes, DownloadCompleteCallback callback) { + _storage = storage; + _downloadUrl = downloadUrl; + _toDir = toDir; + _callback = callback; + _inited = true; + + this.maxTemplateSizeInBytes = maxTemplateSizeInBytes; + } + + @Override + public String getDownloadError() { + return _errorString; + } + + @Override + public String getDownloadLocalPath() { + File file = new File(_toFile); + return file.getAbsolutePath(); + } + + @Override + public int getDownloadPercent() { + if (_remoteSize == 0) { + return 0; + } + + return (int)(100.0 * _totalBytes / _remoteSize); + } + + @Override + public long getDownloadTime() { + return _downloadTime; + } + + @Override + public long getDownloadedBytes() { + return _totalBytes; + } + + @Override + public Status getStatus() { + return _status; + } + + @Override + public void setDownloadError(String string) { + _errorString = string; + } + + @Override + public void setStatus(Status status) { + _status = status; + } + + @Override + public boolean stopDownload() { + switch (getStatus()) { + case IN_PROGRESS: + case UNKNOWN: + case NOT_STARTED: + case RECOVERABLE_ERROR: + case UNRECOVERABLE_ERROR: + case ABORTED: + _status = TemplateDownloader.Status.ABORTED; + break; + case DOWNLOAD_FINISHED: + break; + default: + break; + } + File f = new File(_toFile); + if (f.exists()) { + f.delete(); + } + return true; + } + + @Override + public long getMaxTemplateSizeInBytes() { + return this.maxTemplateSizeInBytes; + } + + @Override + protected void runInContext() { + try { + download(_resume, _callback); + } catch (Exception e) { + s_logger.warn("Unable to complete download due to ", e); + _errorString = "Failed to install: " + e.getMessage(); + _status = TemplateDownloader.Status.UNRECOVERABLE_ERROR; + } + } + + @Override + public void setResume(boolean resume) { + _resume = resume; + + } + + @Override + public boolean isInited() { + return _inited; + } +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/TemplateLocation.java b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/TemplateLocation.java new file mode 100644 index 0000000000..58a4a57b30 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/TemplateLocation.java @@ -0,0 +1,218 @@ +// +// 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.storage.template; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Properties; + +import com.cloud.storage.Storage.ImageFormat; +import com.cloud.storage.StorageLayer; +import com.cloud.storage.template.Processor.FormatInfo; +import com.cloud.utils.NumbersUtil; + +import org.apache.cloudstack.storage.command.DownloadCommand.ResourceType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TemplateLocation { + private static final Logger s_logger = LoggerFactory.getLogger(TemplateLocation.class); + public final static String Filename = "template.properties"; + + StorageLayer _storage; + String _templatePath; + boolean _isCorrupted; + ResourceType _resourceType = ResourceType.TEMPLATE; + + File _file; + Properties _props; + + ArrayList _formats; + + public TemplateLocation(StorageLayer storage, String templatePath) { + _storage = storage; + _templatePath = templatePath; + if (!_templatePath.endsWith(File.separator)) { + _templatePath += File.separator; + } + _formats = new ArrayList(5); + _props = new Properties(); + //TO DO - remove this hack + if (_templatePath.matches(".*" + "volumes" + ".*")) { + _file = _storage.getFile(_templatePath + "volume.properties"); + _resourceType = ResourceType.VOLUME; + } else { + _file = _storage.getFile(_templatePath + Filename); + } + _isCorrupted = false; + } + + public boolean create(long id, boolean isPublic, String uniqueName) throws IOException { + boolean result = load(); + _props.setProperty("id", Long.toString(id)); + _props.setProperty("public", Boolean.toString(isPublic)); + _props.setProperty("uniquename", uniqueName); + + return result; + } + + public boolean purge() { + boolean purged = true; + String[] files = _storage.listFiles(_templatePath); + for (String file : files) { + boolean r = _storage.delete(file); + if (!r) { + purged = false; + } + if (s_logger.isDebugEnabled()) { + s_logger.debug((r ? "R" : "Unable to r") + "emove " + file); + } + } + + return purged; + } + + public boolean load() throws IOException { + try (FileInputStream strm = new FileInputStream(_file);) { + _props.load(strm); + } catch (IOException e) { + s_logger.warn("Unable to load the template properties", e); + } + + for (ImageFormat format : ImageFormat.values()) { + String ext = _props.getProperty(format.getFileExtension()); + if (ext != null) { + FormatInfo info = new FormatInfo(); + info.format = format; + info.filename = _props.getProperty(format.getFileExtension() + ".filename"); + if (info.filename == null) { + continue; + } + info.size = NumbersUtil.parseLong(_props.getProperty(format.getFileExtension() + ".size"), -1); + _props.setProperty("physicalSize", Long.toString(info.size)); + info.virtualSize = NumbersUtil.parseLong(_props.getProperty(format.getFileExtension() + ".virtualsize"), -1); + _formats.add(info); + + if (!checkFormatValidity(info)) { + _isCorrupted = true; + s_logger.warn("Cleaning up inconsistent information for " + format); + } + } + } + + if (_props.getProperty("uniquename") == null || _props.getProperty("virtualsize") == null) { + return false; + } + + return (_formats.size() > 0); + } + + public boolean save() { + for (FormatInfo info : _formats) { + _props.setProperty(info.format.getFileExtension(), "true"); + _props.setProperty(info.format.getFileExtension() + ".filename", info.filename); + _props.setProperty(info.format.getFileExtension() + ".size", Long.toString(info.size)); + _props.setProperty(info.format.getFileExtension() + ".virtualsize", Long.toString(info.virtualSize)); + } + try (FileOutputStream strm = new FileOutputStream(_file);) { + _props.store(strm, ""); + } catch (IOException e) { + s_logger.warn("Unable to save the template properties ", e); + return false; + } + return true; + } + + public TemplateProp getTemplateInfo() { + TemplateProp tmplInfo = new TemplateProp(); + tmplInfo.id = Long.parseLong(_props.getProperty("id")); + tmplInfo.installPath = _templatePath + _props.getProperty("filename"); // _templatePath endsWith / + if (_resourceType == ResourceType.VOLUME) { + tmplInfo.installPath = tmplInfo.installPath.substring(tmplInfo.installPath.indexOf("volumes")); + } else { + tmplInfo.installPath = tmplInfo.installPath.substring(tmplInfo.installPath.indexOf("template")); + } + tmplInfo.isCorrupted = _isCorrupted; + tmplInfo.isPublic = Boolean.parseBoolean(_props.getProperty("public")); + tmplInfo.templateName = _props.getProperty("uniquename"); + if (_props.getProperty("virtualsize") != null) { + tmplInfo.size = Long.parseLong(_props.getProperty("virtualsize")); + } + if (_props.getProperty("size") != null) { + tmplInfo.physicalSize = Long.parseLong(_props.getProperty("size")); + } + + return tmplInfo; + } + + public FormatInfo getFormat(ImageFormat format) { + for (FormatInfo info : _formats) { + if (info.format == format) { + return info; + } + } + + return null; + } + + public boolean addFormat(FormatInfo newInfo) { + deleteFormat(newInfo.format); + + if (!checkFormatValidity(newInfo)) { + s_logger.warn("Format is invalid"); + s_logger.debug("Format: " + newInfo.format + " size: " + newInfo.size + " virtualsize: " + newInfo.virtualSize + " filename: " + newInfo.filename); + s_logger.debug("format, filename cannot be null and size, virtual size should be > 0 "); + return false; + } + + _props.setProperty("virtualsize", Long.toString(newInfo.virtualSize)); + _formats.add(newInfo); + return true; + } + + public void updateVirtualSize(long virtualSize) { + _props.setProperty("virtualsize", Long.toString(virtualSize)); + } + + protected boolean checkFormatValidity(FormatInfo info) { + return (info.format != null && info.size > 0 && info.virtualSize > 0 && info.filename != null); + } + + protected FormatInfo deleteFormat(ImageFormat format) { + Iterator it = _formats.iterator(); + while (it.hasNext()) { + FormatInfo info = it.next(); + if (info.format == format) { + it.remove(); + _props.remove(format.getFileExtension()); + _props.remove(format.getFileExtension() + ".filename"); + _props.remove(format.getFileExtension() + ".size"); + _props.remove(format.getFileExtension() + ".virtualsize"); + return info; + } + } + + return null; + } +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/TemplateUploader.java b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/TemplateUploader.java new file mode 100644 index 0000000000..ebe567a110 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/TemplateUploader.java @@ -0,0 +1,89 @@ +// +// 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.storage.template; + +public interface TemplateUploader extends Runnable { + + /** + * Callback used to notify completion of upload + * + */ + public interface UploadCompleteCallback { + void uploadComplete(Status status); + + } + + public static enum Status { + UNKNOWN, NOT_STARTED, IN_PROGRESS, ABORTED, UNRECOVERABLE_ERROR, RECOVERABLE_ERROR, UPLOAD_FINISHED, POST_UPLOAD_FINISHED + } + + /** + * Initiate upload + * @param callback completion callback to be called after upload is complete + * @return bytes uploaded + */ + public long upload(UploadCompleteCallback callback); + + /** + * @return + */ + public boolean stopUpload(); + + /** + * @return percent of file uploaded + */ + public int getUploadPercent(); + + /** + * Get the status of the upload + * @return status of upload + */ + public TemplateUploader.Status getStatus(); + + /** + * Get time taken to upload so far + * @return time in seconds taken to upload + */ + public long getUploadTime(); + + /** + * Get bytes uploaded + * @return bytes uploaded so far + */ + public long getUploadedBytes(); + + /** + * Get the error if any + * @return error string if any + */ + public String getUploadError(); + + /** Get local path of the uploaded file + * @return local path of the file uploaded + */ + public String getUploadLocalPath(); + + public void setStatus(TemplateUploader.Status status); + + public void setUploadError(String string); + + public void setResume(boolean resume); + +} diff --git a/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/VhdProcessor.java b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/VhdProcessor.java new file mode 100644 index 0000000000..d242691ada --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/com/cloud/storage/template/VhdProcessor.java @@ -0,0 +1,132 @@ +// +// 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.storage.template; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Map; + +import javax.naming.ConfigurationException; + +import com.cloud.exception.InternalErrorException; +import com.cloud.storage.Storage.ImageFormat; +import com.cloud.storage.StorageLayer; +import com.cloud.utils.NumbersUtil; +import com.cloud.utils.component.AdapterBase; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * VhdProcessor processes the downloaded template for VHD. It + * currently does not handle any type of template conversion + * into the VHD format. + * + */ +public class VhdProcessor extends AdapterBase implements Processor { + + private static final Logger s_logger = LoggerFactory.getLogger(VhdProcessor.class); + StorageLayer _storage; + private int vhdFooterSize = 512; + private int vhdFooterCreatorAppOffset = 28; + private int vhdFooterCreatorVerOffset = 32; + private int vhdFooterCurrentSizeOffset = 48; + private byte[][] citrixCreatorApp = { {0x74, 0x61, 0x70, 0x00}, {0x43, 0x54, 0x58, 0x53}}; /*"tap ", and "CTXS"*/ + + @Override + public FormatInfo process(String templatePath, ImageFormat format, String templateName) throws InternalErrorException { + if (format != null) { + s_logger.debug("We currently don't handle conversion from " + format + " to VHD."); + return null; + } + + String vhdPath = templatePath + File.separator + templateName + "." + ImageFormat.VHD.getFileExtension(); + if (!_storage.exists(vhdPath)) { + s_logger.debug("Unable to find the vhd file: " + vhdPath); + return null; + } + + File vhdFile = _storage.getFile(vhdPath); + + FormatInfo info = new FormatInfo(); + info.format = ImageFormat.VHD; + info.filename = templateName + "." + ImageFormat.VHD.getFileExtension(); + info.size = _storage.getSize(vhdPath); + + try { + info.virtualSize = getTemplateVirtualSize(vhdFile); + } catch (IOException e) { + s_logger.error("Unable to get the virtual size for " + vhdPath); + throw new InternalErrorException("unable to get virtual size from vhd file"); + } + + return info; + } + + @Override + public long getVirtualSize(File file) throws IOException { + try { + long size = getTemplateVirtualSize(file); + return size; + } catch (Exception e) { + s_logger.info("[ignored]" + "failed to get template virtual size for VHD: " + e.getLocalizedMessage()); + } + return file.length(); + } + + protected long getTemplateVirtualSize(File file) throws IOException { + byte[] currentSize = new byte[8]; + byte[] creatorApp = new byte[4]; + + try (FileInputStream strm = new FileInputStream(file)) { + long skipped = strm.skip(file.length() - vhdFooterSize + vhdFooterCreatorAppOffset); + if (skipped == -1) { + throw new IOException("Unexpected end-of-file"); + } + long read = strm.read(creatorApp); + if (read == -1) { + throw new IOException("Unexpected end-of-file"); + } + skipped = strm.skip(vhdFooterCurrentSizeOffset - vhdFooterCreatorVerOffset); + if (skipped == -1) { + throw new IOException("Unexpected end-of-file"); + } + read = strm.read(currentSize); + if (read == -1) { + throw new IOException("Unexpected end-of-file"); + } + } + + return NumbersUtil.bytesToLong(currentSize); + } + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + _name = name; + _storage = (StorageLayer)params.get(StorageLayer.InstanceConfigKey); + if (_storage == null) { + throw new ConfigurationException("Unable to get storage implementation"); + } + + return true; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/AttachAnswer.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/AttachAnswer.java new file mode 100644 index 0000000000..80736891cb --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/AttachAnswer.java @@ -0,0 +1,48 @@ +// +// 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 org.apache.cloudstack.storage.command; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.to.DiskTO; + +public class AttachAnswer extends Answer { + private DiskTO disk; + + public AttachAnswer() { + super(null); + } + + public AttachAnswer(DiskTO disk) { + super(null); + setDisk(disk); + } + + public AttachAnswer(String errMsg) { + super(null, false, errMsg); + } + + public DiskTO getDisk() { + return disk; + } + + public void setDisk(DiskTO disk) { + this.disk = disk; + } +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/AttachCommand.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/AttachCommand.java new file mode 100644 index 0000000000..d15a4e42da --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/AttachCommand.java @@ -0,0 +1,76 @@ +// +// 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 org.apache.cloudstack.storage.command; + +import java.util.Map; + +import com.cloud.agent.api.to.DiskTO; + +public final class AttachCommand extends StorageSubSystemCommand { + private DiskTO disk; + private String vmName; + private boolean inSeq = false; + private Map controllerInfo; + + public AttachCommand(final DiskTO disk, final String vmName) { + super(); + this.disk = disk; + this.vmName = vmName; + } + public AttachCommand(DiskTO disk, String vmName, Map controllerInfo) { + super(); + this.disk = disk; + this.vmName = vmName; + this.controllerInfo = controllerInfo; + } + + public Map getControllerInfo() { + return controllerInfo; + } + public void setControllerInfo(Map controllerInfo) { + this.controllerInfo = controllerInfo; + } + + @Override + public boolean executeInSequence() { + return false; + } + + public DiskTO getDisk() { + return disk; + } + + public void setDisk(final DiskTO disk) { + this.disk = disk; + } + + public String getVmName() { + return vmName; + } + + public void setVmName(final String vmName) { + this.vmName = vmName; + } + + @Override + public void setExecuteInSequence(final boolean inSeq) { + this.inSeq = inSeq; + } +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/AttachPrimaryDataStoreAnswer.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/AttachPrimaryDataStoreAnswer.java new file mode 100644 index 0000000000..0c1f170857 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/AttachPrimaryDataStoreAnswer.java @@ -0,0 +1,57 @@ +// +// 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 org.apache.cloudstack.storage.command; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; + +public class AttachPrimaryDataStoreAnswer extends Answer { + private String uuid; + private long capacity; + private long avail; + + public AttachPrimaryDataStoreAnswer(Command cmd) { + super(cmd); + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getUuid() { + return uuid; + } + + public void setCapacity(long capacity) { + this.capacity = capacity; + } + + public long getCapacity() { + return capacity; + } + + public void setAvailable(long avail) { + this.avail = avail; + } + + public long getAvailable() { + return avail; + } +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/AttachPrimaryDataStoreCmd.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/AttachPrimaryDataStoreCmd.java new file mode 100644 index 0000000000..9f4d14eb7a --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/AttachPrimaryDataStoreCmd.java @@ -0,0 +1,44 @@ +// +// 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 org.apache.cloudstack.storage.command; + + +public final class AttachPrimaryDataStoreCmd extends StorageSubSystemCommand { + @Override + public void setExecuteInSequence(final boolean inSeq) { + + } + + private final String dataStore; + + public AttachPrimaryDataStoreCmd(final String uri) { + super(); + dataStore = uri; + } + + public String getDataStore() { + return dataStore; + } + + @Override + public boolean executeInSequence() { + return false; + } +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/CopyCmdAnswer.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/CopyCmdAnswer.java new file mode 100644 index 0000000000..9b581decd6 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/CopyCmdAnswer.java @@ -0,0 +1,40 @@ +// +// 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 org.apache.cloudstack.storage.command; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.to.DataTO; + +public class CopyCmdAnswer extends Answer { + private DataTO newData; + + public CopyCmdAnswer(DataTO newData) { + super(null); + this.newData = newData; + } + + public DataTO getNewData() { + return this.newData; + } + + public CopyCmdAnswer(String errMsg) { + super(null, false, errMsg); + } +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/CopyCommand.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/CopyCommand.java new file mode 100644 index 0000000000..f1895a4ae5 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/CopyCommand.java @@ -0,0 +1,96 @@ +// +// 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 org.apache.cloudstack.storage.command; + +import java.util.HashMap; +import java.util.Map; + +import com.cloud.agent.api.to.DataTO; + +public final class CopyCommand extends StorageSubSystemCommand { + private DataTO srcTO; + private DataTO destTO; + private DataTO cacheTO; + private boolean executeInSequence = false; + private Map options = new HashMap(); + private Map options2 = new HashMap(); + + public CopyCommand(final DataTO srcData, final DataTO destData, final int timeout, final boolean executeInSequence) { + super(); + srcTO = srcData; + destTO = destData; + setWait(timeout); + this.executeInSequence = executeInSequence; + } + + public DataTO getDestTO() { + return destTO; + } + + public void setSrcTO(final DataTO srcTO) { + this.srcTO = srcTO; + } + + public void setDestTO(final DataTO destTO) { + this.destTO = destTO; + } + + public DataTO getSrcTO() { + return srcTO; + } + + @Override + public boolean executeInSequence() { + return executeInSequence; + } + + public DataTO getCacheTO() { + return cacheTO; + } + + public void setCacheTO(final DataTO cacheTO) { + this.cacheTO = cacheTO; + } + + public int getWaitInMillSeconds() { + return getWait() * 1000; + } + + public void setOptions(final Map options) { + this.options = options; + } + + public Map getOptions() { + return options; + } + + public void setOptions2(final Map options2) { + this.options2 = options2; + } + + public Map getOptions2() { + return options2; + } + + @Override + public void setExecuteInSequence(final boolean inSeq) { + executeInSequence = inSeq; + } +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/CreateObjectAnswer.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/CreateObjectAnswer.java new file mode 100644 index 0000000000..b6dcfb71dd --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/CreateObjectAnswer.java @@ -0,0 +1,44 @@ +// +// 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 org.apache.cloudstack.storage.command; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.to.DataTO; + +public final class CreateObjectAnswer extends Answer { + private DataTO data; + + protected CreateObjectAnswer() { + super(); + } + + public CreateObjectAnswer(DataTO data) { + super(); + this.data = data; + } + + public DataTO getData() { + return data; + } + + public CreateObjectAnswer(String errMsg) { + super(null, false, errMsg); + } +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/CreateObjectCommand.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/CreateObjectCommand.java new file mode 100644 index 0000000000..88a582d031 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/CreateObjectCommand.java @@ -0,0 +1,49 @@ +// +// 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 org.apache.cloudstack.storage.command; + +import com.cloud.agent.api.to.DataTO; + +public final class CreateObjectCommand extends StorageSubSystemCommand { + private DataTO data; + + public CreateObjectCommand(final DataTO obj) { + super(); + data = obj; + } + + protected CreateObjectCommand() { + super(); + } + + @Override + public boolean executeInSequence() { + return false; + } + + public DataTO getData() { + return data; + } + + @Override + public void setExecuteInSequence(final boolean inSeq) { + + } +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/CreatePrimaryDataStoreCmd.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/CreatePrimaryDataStoreCmd.java new file mode 100644 index 0000000000..b1f32a9d32 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/CreatePrimaryDataStoreCmd.java @@ -0,0 +1,44 @@ +// +// 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 org.apache.cloudstack.storage.command; + + +public final class CreatePrimaryDataStoreCmd extends StorageSubSystemCommand { + private final String dataStore; + + public CreatePrimaryDataStoreCmd(final String uri) { + super(); + dataStore = uri; + } + + public String getDataStore() { + return dataStore; + } + + @Override + public boolean executeInSequence() { + return false; + } + + @Override + public void setExecuteInSequence(final boolean inSeq) { + + } +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/DeleteCommand.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/DeleteCommand.java new file mode 100644 index 0000000000..6f82fa9781 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/DeleteCommand.java @@ -0,0 +1,49 @@ +// +// 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 org.apache.cloudstack.storage.command; + +import com.cloud.agent.api.to.DataTO; + +public final class DeleteCommand extends StorageSubSystemCommand { + private DataTO data; + + public DeleteCommand(final DataTO data) { + super(); + this.data = data; + } + + protected DeleteCommand() { + super(); + } + + @Override + public boolean executeInSequence() { + return false; + } + + public DataTO getData() { + return data; + } + + @Override + public void setExecuteInSequence(final boolean inSeq) { + + } +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/DettachAnswer.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/DettachAnswer.java new file mode 100644 index 0000000000..a28f47cd95 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/DettachAnswer.java @@ -0,0 +1,48 @@ +// +// 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 org.apache.cloudstack.storage.command; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.to.DiskTO; + +public final class DettachAnswer extends Answer { + private DiskTO disk; + + public DettachAnswer() { + super(null); + } + + public DettachAnswer(DiskTO disk) { + super(null); + setDisk(disk); + } + + public DettachAnswer(String errMsg) { + super(null, false, errMsg); + } + + public DiskTO getDisk() { + return disk; + } + + public void setDisk(DiskTO disk) { + this.disk = disk; + } +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/DettachCommand.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/DettachCommand.java new file mode 100644 index 0000000000..8d89dd501e --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/DettachCommand.java @@ -0,0 +1,95 @@ +// +// 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 org.apache.cloudstack.storage.command; + +import com.cloud.agent.api.to.DiskTO; + +public class DettachCommand extends StorageSubSystemCommand { + private DiskTO disk; + private String vmName; + private boolean _managed; + private String _iScsiName; + private String _storageHost; + private int _storagePort; + + public DettachCommand(final DiskTO disk, final String vmName) { + super(); + this.disk = disk; + this.vmName = vmName; + } + + @Override + public boolean executeInSequence() { + return false; + } + + public DiskTO getDisk() { + return disk; + } + + public void setDisk(final DiskTO disk) { + this.disk = disk; + } + + public String getVmName() { + return vmName; + } + + public void setVmName(final String vmName) { + this.vmName = vmName; + } + + public void setManaged(final boolean managed) { + _managed = managed; + } + + public boolean isManaged() { + return _managed; + } + + public void set_iScsiName(final String iScsiName) { + _iScsiName = iScsiName; + } + + public String get_iScsiName() { + return _iScsiName; + } + + public void setStorageHost(final String storageHost) { + _storageHost = storageHost; + } + + public String getStorageHost() { + return _storageHost; + } + + public void setStoragePort(final int storagePort) { + _storagePort = storagePort; + } + + public int getStoragePort() { + return _storagePort; + } + + @Override + public void setExecuteInSequence(final boolean inSeq) { + + } +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/DownloadCommand.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/DownloadCommand.java new file mode 100644 index 0000000000..7fddef4a0e --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/DownloadCommand.java @@ -0,0 +1,184 @@ +// +// 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 org.apache.cloudstack.storage.command; + +import com.cloud.agent.api.storage.AbstractDownloadCommand; +import com.cloud.agent.api.storage.PasswordAuth; +import com.cloud.agent.api.to.DataStoreTO; +import com.cloud.agent.api.to.NfsTO; +import com.cloud.storage.Storage.ImageFormat; +import com.cloud.utils.net.Proxy; + +import org.apache.cloudstack.api.InternalIdentity; +import org.apache.cloudstack.storage.to.TemplateObjectTO; +import org.apache.cloudstack.storage.to.VolumeObjectTO; + +public class DownloadCommand extends AbstractDownloadCommand implements InternalIdentity { + + public static enum ResourceType { + VOLUME, TEMPLATE + } + + private boolean hvm; + private String description; + private String checksum; + private PasswordAuth auth; + private Proxy _proxy; + private Long maxDownloadSizeInBytes = null; + private long id; + private ResourceType resourceType = ResourceType.TEMPLATE; + private String installPath; + private DataStoreTO _store; + private DataStoreTO cacheStore; + + protected DownloadCommand() { + } + + public DownloadCommand(DownloadCommand that) { + super(that); + hvm = that.hvm; + checksum = that.checksum; + id = that.id; + description = that.description; + auth = that.getAuth(); + setSecUrl(that.getSecUrl()); + maxDownloadSizeInBytes = that.getMaxDownloadSizeInBytes(); + resourceType = that.resourceType; + installPath = that.installPath; + _store = that._store; + _proxy = that._proxy; + } + + public DownloadCommand(TemplateObjectTO template, Long maxDownloadSizeInBytes) { + + super(template.getName(), template.getOrigUrl(), template.getFormat(), template.getAccountId()); + _store = template.getDataStore(); + installPath = template.getPath(); + hvm = template.isRequiresHvm(); + checksum = template.getChecksum(); + id = template.getId(); + description = template.getDescription(); + if (_store instanceof NfsTO) { + setSecUrl(((NfsTO)_store).getUrl()); + } + this.maxDownloadSizeInBytes = maxDownloadSizeInBytes; + } + + public DownloadCommand(TemplateObjectTO template, String user, String passwd, Long maxDownloadSizeInBytes) { + this(template, maxDownloadSizeInBytes); + auth = new PasswordAuth(user, passwd); + } + + public DownloadCommand(VolumeObjectTO volume, Long maxDownloadSizeInBytes, String checkSum, String url, ImageFormat format) { + super(volume.getName(), url, format, volume.getAccountId()); + checksum = checkSum; + id = volume.getVolumeId(); + installPath = volume.getPath(); + _store = volume.getDataStore(); + this.maxDownloadSizeInBytes = maxDownloadSizeInBytes; + resourceType = ResourceType.VOLUME; + } + + @Override + public long getId() { + return id; + } + + public void setHvm(boolean hvm) { + this.hvm = hvm; + } + + public boolean isHvm() { + return hvm; + } + + public String getDescription() { + return description; + } + + public String getChecksum() { + return checksum; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setChecksum(String checksum) { + this.checksum = checksum; + } + + @Override + public boolean executeInSequence() { + return false; + } + + public PasswordAuth getAuth() { + return auth; + } + + public void setCreds(String userName, String passwd) { + auth = new PasswordAuth(userName, passwd); + } + + public Proxy getProxy() { + return _proxy; + } + + public void setProxy(Proxy proxy) { + _proxy = proxy; + } + + public Long getMaxDownloadSizeInBytes() { + return maxDownloadSizeInBytes; + } + + public ResourceType getResourceType() { + return resourceType; + } + + public void setResourceType(ResourceType resourceType) { + this.resourceType = resourceType; + } + + public DataStoreTO getDataStore() { + return _store; + } + + public void setDataStore(DataStoreTO store) { + this._store = store; + } + + public String getInstallPath() { + return installPath; + } + + public void setInstallPath(String installPath) { + this.installPath = installPath; + } + + public void setCacheStore(DataStoreTO cacheStore) { + this.cacheStore = cacheStore; + } + + public DataStoreTO getCacheStore() { + return cacheStore; + } +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/DownloadProgressCommand.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/DownloadProgressCommand.java new file mode 100644 index 0000000000..b6bcfbb068 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/DownloadProgressCommand.java @@ -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. +// + +package org.apache.cloudstack.storage.command; + +public class DownloadProgressCommand extends DownloadCommand { + public static enum RequestType { + GET_STATUS, ABORT, RESTART, PURGE, GET_OR_RESTART + } + + private String jobId; + private RequestType request; + + protected DownloadProgressCommand() { + super(); + } + + public DownloadProgressCommand(DownloadCommand cmd, String jobId, RequestType req) { + super(cmd); + + this.jobId = jobId; + this.setRequest(req); + } + + public String getJobId() { + return jobId; + } + + public void setRequest(RequestType request) { + this.request = request; + } + + public RequestType getRequest() { + return request; + } +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/ForgetObjectCmd.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/ForgetObjectCmd.java new file mode 100644 index 0000000000..c47ee882c9 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/ForgetObjectCmd.java @@ -0,0 +1,44 @@ +// +// 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 org.apache.cloudstack.storage.command; + +import com.cloud.agent.api.to.DataTO; + +public class ForgetObjectCmd extends StorageSubSystemCommand { + private final DataTO dataTO; + + public ForgetObjectCmd(final DataTO data) { + dataTO = data; + } + + public DataTO getDataTO() { + return dataTO; + } + + @Override + public boolean executeInSequence() { + return false; + } + + @Override + public void setExecuteInSequence(final boolean inSeq) { + + } +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/IntroduceObjectAnswer.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/IntroduceObjectAnswer.java new file mode 100644 index 0000000000..8fa23a4198 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/IntroduceObjectAnswer.java @@ -0,0 +1,35 @@ +// +// 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 org.apache.cloudstack.storage.command; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.to.DataTO; + +public class IntroduceObjectAnswer extends Answer { + private DataTO dataTO; + + public IntroduceObjectAnswer(DataTO dataTO) { + this.dataTO = dataTO; + } + + public DataTO getDataTO() { + return dataTO; + } +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/IntroduceObjectCmd.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/IntroduceObjectCmd.java new file mode 100644 index 0000000000..8c3bc5e5cc --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/IntroduceObjectCmd.java @@ -0,0 +1,44 @@ +// +// 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 org.apache.cloudstack.storage.command; + +import com.cloud.agent.api.to.DataTO; + +public class IntroduceObjectCmd extends StorageSubSystemCommand { + private final DataTO dataTO; + + public IntroduceObjectCmd(final DataTO dataTO) { + this.dataTO = dataTO; + } + + public DataTO getDataTO() { + return dataTO; + } + + @Override + public boolean executeInSequence() { + return false; + } + + @Override + public void setExecuteInSequence(final boolean inSeq) { + + } +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/RevertSnapshotCommand.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/RevertSnapshotCommand.java new file mode 100644 index 0000000000..1a4403b3ba --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/RevertSnapshotCommand.java @@ -0,0 +1,49 @@ +/* + * 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 org.apache.cloudstack.storage.command; + +import org.apache.cloudstack.storage.to.SnapshotObjectTO; + +public final class RevertSnapshotCommand extends StorageSubSystemCommand { + private SnapshotObjectTO data; + private boolean _executeInSequence = false; + + public RevertSnapshotCommand(SnapshotObjectTO data) { + super(); + this.data = data; + } + + protected RevertSnapshotCommand() { + super(); + } + + public SnapshotObjectTO getData() { + return this.data; + } + + @Override + public void setExecuteInSequence(final boolean executeInSequence) { + _executeInSequence = executeInSequence; + } + + @Override + public boolean executeInSequence() { + return _executeInSequence; + } +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/SnapshotAndCopyAnswer.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/SnapshotAndCopyAnswer.java new file mode 100644 index 0000000000..b99d182a65 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/SnapshotAndCopyAnswer.java @@ -0,0 +1,41 @@ +// +// 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 org.apache.cloudstack.storage.command; + +import com.cloud.agent.api.Answer; + +public class SnapshotAndCopyAnswer extends Answer { + private String _path; + + public SnapshotAndCopyAnswer() { + } + + public SnapshotAndCopyAnswer(String errMsg) { + super(null, false, errMsg); + } + + public void setPath(String path) { + _path = path; + } + + public String getPath() { + return _path; + } +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/SnapshotAndCopyCommand.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/SnapshotAndCopyCommand.java new file mode 100644 index 0000000000..0a1a84d4fc --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/SnapshotAndCopyCommand.java @@ -0,0 +1,58 @@ +// +// 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 org.apache.cloudstack.storage.command; + +import java.util.Map; + +public final class SnapshotAndCopyCommand extends StorageSubSystemCommand { + private final String _uuidOfSourceVdi; + private final Map _sourceDetails; + private final Map _destDetails; + + private boolean _executeInSequence = true; + + public SnapshotAndCopyCommand(final String uuidOfSourceVdi, final Map sourceDetails, final Map destDetails) { + _uuidOfSourceVdi = uuidOfSourceVdi; + _sourceDetails = sourceDetails; + _destDetails = destDetails; + } + + public String getUuidOfSourceVdi() { + return _uuidOfSourceVdi; + } + + public Map getSourceDetails() { + return _sourceDetails; + } + + public Map getDestDetails() { + return _destDetails; + } + + @Override + public void setExecuteInSequence(final boolean executeInSequence) { + _executeInSequence = executeInSequence; + } + + @Override + public boolean executeInSequence() { + return _executeInSequence; + } +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/StorageSubSystemCommand.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/StorageSubSystemCommand.java new file mode 100644 index 0000000000..f50a5fb575 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/StorageSubSystemCommand.java @@ -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. +// + +package org.apache.cloudstack.storage.command; + +import com.cloud.agent.api.Command; + +public abstract class StorageSubSystemCommand extends Command { + public abstract void setExecuteInSequence(boolean inSeq); +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/TemplateOrVolumePostUploadCommand.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/TemplateOrVolumePostUploadCommand.java new file mode 100644 index 0000000000..5d1e56bd77 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/TemplateOrVolumePostUploadCommand.java @@ -0,0 +1,199 @@ +/* + * 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 org.apache.cloudstack.storage.command; + +public class TemplateOrVolumePostUploadCommand { + + long entityId; + + String entityUUID; + + String absolutePath; + + String checksum; + + String type; + + String name; + + String localPath; + + boolean requiresHvm; + + String imageFormat; + + String dataTo; + + String dataToRole; + + String remoteEndPoint; + + String maxUploadSize; + + String description; + + private String defaultMaxAccountSecondaryStorage; + + private long accountId; + + public TemplateOrVolumePostUploadCommand(long entityId, String entityUUID, String absolutePath, String checksum, String type, String name, String imageFormat, String dataTo, + String dataToRole) { + this.entityId = entityId; + this.entityUUID = entityUUID; + this.absolutePath = absolutePath; + this.checksum = checksum; + this.type = type; + this.name = name; + this.imageFormat = imageFormat; + this.dataTo = dataTo; + this.dataToRole = dataToRole; + } + + public TemplateOrVolumePostUploadCommand() { + } + + public String getRemoteEndPoint() { + return remoteEndPoint; + } + + public void setRemoteEndPoint(String remoteEndPoint) { + this.remoteEndPoint = remoteEndPoint; + } + + public String getDataTo() { + return dataTo; + } + + public void setDataTo(String dataTo) { + this.dataTo = dataTo; + } + + public String getDataToRole() { + return dataToRole; + } + + public void setDataToRole(String dataToRole) { + this.dataToRole = dataToRole; + } + + public String getLocalPath() { + return localPath; + } + + public void setLocalPath(String localPath) { + this.localPath = localPath; + } + + public boolean getRequiresHvm() { + return requiresHvm; + } + + public void setRequiresHvm(boolean requiresHvm) { + this.requiresHvm = requiresHvm; + } + + public String getImageFormat() { + return imageFormat; + } + + public void setImageFormat(String imageFormat) { + this.imageFormat = imageFormat; + } + + public long getEntityId() { + return entityId; + } + + public void setEntityId(long entityId) { + this.entityId = entityId; + } + + public String getEntityUUID() { + return entityUUID; + } + + public void setEntityUUID(String entityUUID) { + this.entityUUID = entityUUID; + } + + public String getAbsolutePath() { + return absolutePath; + } + + public void setAbsolutePath(String absolutePath) { + this.absolutePath = absolutePath; + } + + public String getChecksum() { + return checksum; + } + + public void setChecksum(String checksum) { + this.checksum = checksum; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getMaxUploadSize() { + return maxUploadSize; + } + + public void setMaxUploadSize(String maxUploadSize) { + this.maxUploadSize = maxUploadSize; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setDefaultMaxAccountSecondaryStorage(String defaultMaxAccountSecondaryStorage) { + this.defaultMaxAccountSecondaryStorage = defaultMaxAccountSecondaryStorage; + } + + public String getDefaultMaxAccountSecondaryStorage() { + return defaultMaxAccountSecondaryStorage; + } + + public void setAccountId(long accountId) { + this.accountId = accountId; + } + + public long getAccountId() { + return accountId; + } +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/UploadStatusAnswer.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/UploadStatusAnswer.java new file mode 100644 index 0000000000..1825b5a03d --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/UploadStatusAnswer.java @@ -0,0 +1,88 @@ +// +// 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 org.apache.cloudstack.storage.command; + +import com.cloud.agent.api.Answer; + +public class UploadStatusAnswer extends Answer { + public static enum UploadStatus { + UNKNOWN, IN_PROGRESS, COMPLETED, ERROR + } + + private UploadStatus status; + private long virtualSize = 0; + private long physicalSize = 0; + private String installPath = null; + private int downloadPercent = 0; + + protected UploadStatusAnswer() { + } + + public UploadStatusAnswer(UploadStatusCommand cmd, UploadStatus status, String msg) { + super(cmd, false, msg); + this.status = status; + } + + public UploadStatusAnswer(UploadStatusCommand cmd, Exception e) { + super(cmd, false, e.getMessage()); + this.status = UploadStatus.ERROR; + } + + public UploadStatusAnswer(UploadStatusCommand cmd, UploadStatus status) { + super(cmd, true, null); + this.status = status; + } + + public UploadStatus getStatus() { + return status; + } + + public long getVirtualSize() { + return virtualSize; + } + + public void setVirtualSize(long virtualSize) { + this.virtualSize = virtualSize; + } + + public long getPhysicalSize() { + return physicalSize; + } + + public void setPhysicalSize(long physicalSize) { + this.physicalSize = physicalSize; + } + + public String getInstallPath() { + return installPath; + } + + public void setInstallPath(String installPath) { + this.installPath = installPath; + } + + public int getDownloadPercent() { + return downloadPercent; + } + + public void setDownloadPercent(int downloadPercent) { + this.downloadPercent = downloadPercent; + } +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/UploadStatusCommand.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/UploadStatusCommand.java new file mode 100644 index 0000000000..9e6b76e467 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/command/UploadStatusCommand.java @@ -0,0 +1,53 @@ +// +// 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 org.apache.cloudstack.storage.command; + +import com.cloud.agent.api.Command; + +public class UploadStatusCommand extends Command { + public enum EntityType { + Volume, + Template + } + private String entityUuid; + private EntityType entityType; + + protected UploadStatusCommand() { + } + + public UploadStatusCommand(String entityUuid, EntityType entityType) { + this.entityUuid = entityUuid; + this.entityType = entityType; + } + + public String getEntityUuid() { + return entityUuid; + } + + public EntityType getEntityType() { + return entityType; + } + + @Override + public boolean executeInSequence() { + return false; + } + +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/to/ImageStoreTO.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/to/ImageStoreTO.java new file mode 100644 index 0000000000..e2c3ce64c8 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/to/ImageStoreTO.java @@ -0,0 +1,110 @@ +// +// 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 org.apache.cloudstack.storage.to; + +import com.cloud.agent.api.to.DataStoreTO; +import com.cloud.storage.DataStoreRole; + +import org.apache.cloudstack.storage.image.datastore.ImageStoreInfo; + +public class ImageStoreTO implements DataStoreTO { + private String type; + private String uri; + private String providerName; + private DataStoreRole role; + private String uuid; + private static final String pathSeparator = "/"; + + public ImageStoreTO() { + + } + + public ImageStoreTO(ImageStoreInfo dataStore) { + this.type = dataStore.getType(); + this.uri = dataStore.getUri(); + this.providerName = null; + this.role = dataStore.getRole(); + } + + public String getProtocol() { + return this.type; + } + + public String getUri() { + return this.uri; + } + + public String getProviderName() { + return providerName; + } + + public void setType(String type) { + this.type = type; + } + + public void setUri(String uri) { + this.uri = uri; + } + + public void setProviderName(String providerName) { + this.providerName = providerName; + } + + public void setRole(DataStoreRole role) { + this.role = role; + } + + @Override + public DataStoreRole getRole() { + return this.role; + } + + @Override + public String toString() { + return new StringBuilder("ImageStoreTO[type=").append(type) + .append("|provider=") + .append(providerName) + .append("|role=") + .append(role) + .append("|uri=") + .append(uri) + .append("]") + .toString(); + } + + @Override + public String getUuid() { + return uuid; + } + + @Override + public String getUrl() { + return getUri(); + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + @Override + public String getPathSeparator() { + return pathSeparator; + } +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/to/PrimaryDataStoreTO.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/to/PrimaryDataStoreTO.java new file mode 100644 index 0000000000..df1433519f --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/to/PrimaryDataStoreTO.java @@ -0,0 +1,146 @@ +// +// 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 org.apache.cloudstack.storage.to; + +import java.util.Map; + +import com.cloud.agent.api.to.DataStoreTO; +import com.cloud.storage.DataStoreRole; +import com.cloud.storage.Storage.StoragePoolType; + +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore; + +public class PrimaryDataStoreTO implements DataStoreTO { + public static final String MANAGED = PrimaryDataStore.MANAGED; + public static final String STORAGE_HOST = PrimaryDataStore.STORAGE_HOST; + public static final String STORAGE_PORT = PrimaryDataStore.STORAGE_PORT; + public static final String MANAGED_STORE_TARGET = PrimaryDataStore.MANAGED_STORE_TARGET; + public static final String MANAGED_STORE_TARGET_ROOT_VOLUME = PrimaryDataStore.MANAGED_STORE_TARGET_ROOT_VOLUME; + public static final String CHAP_INITIATOR_USERNAME = PrimaryDataStore.CHAP_INITIATOR_USERNAME; + public static final String CHAP_INITIATOR_SECRET = PrimaryDataStore.CHAP_INITIATOR_SECRET; + public static final String CHAP_TARGET_USERNAME = PrimaryDataStore.CHAP_TARGET_USERNAME; + public static final String CHAP_TARGET_SECRET = PrimaryDataStore.CHAP_TARGET_SECRET; + public static final String VOLUME_SIZE = PrimaryDataStore.VOLUME_SIZE; + + private final String uuid; + private final String name; + private String type; + private final long id; + private StoragePoolType poolType; + private String host; + private String path; + private int port; + private final String url; + private Map details; + private static final String pathSeparator = "/"; + + public PrimaryDataStoreTO(PrimaryDataStore dataStore) { + this.uuid = dataStore.getUuid(); + this.name = dataStore.getName(); + this.id = dataStore.getId(); + this.setPoolType(dataStore.getPoolType()); + this.setHost(dataStore.getHostAddress()); + this.setPath(dataStore.getPath()); + this.setPort(dataStore.getPort()); + this.url = dataStore.getUri(); + this.details = dataStore.getDetails(); + } + + public long getId() { + return this.id; + } + + @Override + public String getUuid() { + return this.uuid; + } + + @Override + public String getUrl() { + return this.url; + } + + public Map getDetails() { + return this.details; + } + + public String getName() { + return this.name; + } + + public String getType() { + return this.type; + } + + @Override + public DataStoreRole getRole() { + return DataStoreRole.Primary; + } + + public StoragePoolType getPoolType() { + return poolType; + } + + public void setPoolType(StoragePoolType poolType) { + this.poolType = poolType; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + @Override + public String getPathSeparator() { + return pathSeparator; + } + + @Override + public String toString() { + return new StringBuilder("PrimaryDataStoreTO[uuid=").append(uuid) + .append("|name=") + .append(name) + .append("|id=") + .append(id) + .append("|pooltype=") + .append(poolType) + .append("]") + .toString(); + } +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/to/SnapshotObjectTO.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/to/SnapshotObjectTO.java new file mode 100644 index 0000000000..b580553962 --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/to/SnapshotObjectTO.java @@ -0,0 +1,175 @@ +// +// 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 org.apache.cloudstack.storage.to; + +import java.util.ArrayList; + +import com.cloud.agent.api.to.DataObjectType; +import com.cloud.agent.api.to.DataStoreTO; +import com.cloud.agent.api.to.DataTO; +import com.cloud.hypervisor.Hypervisor.HypervisorType; + +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.commons.lang.ArrayUtils; + +public class SnapshotObjectTO implements DataTO { + private String path; + private VolumeObjectTO volume; + private String parentSnapshotPath; + private DataStoreTO dataStore; + private String vmName; + private String name; + private HypervisorType hypervisorType; + private long id; + private boolean quiescevm; + private String[] parents; + private Long physicalSize = (long) 0; + + + public SnapshotObjectTO() { + + } + + public SnapshotObjectTO(SnapshotInfo snapshot) { + this.path = snapshot.getPath(); + this.setId(snapshot.getId()); + VolumeInfo vol = snapshot.getBaseVolume(); + if (vol != null) { + this.volume = (VolumeObjectTO)vol.getTO(); + this.setVmName(vol.getAttachedVmName()); + } + + SnapshotInfo parentSnapshot = snapshot.getParent(); + ArrayList parentsArry = new ArrayList(); + if (parentSnapshot != null) { + this.parentSnapshotPath = parentSnapshot.getPath(); + while(parentSnapshot != null) { + parentsArry.add(parentSnapshot.getPath()); + parentSnapshot = parentSnapshot.getParent(); + } + parents = parentsArry.toArray(new String[parentsArry.size()]); + ArrayUtils.reverse(parents); + } + + this.dataStore = snapshot.getDataStore().getTO(); + this.setName(snapshot.getName()); + this.hypervisorType = snapshot.getHypervisorType(); + this.quiescevm = false; + } + + @Override + public DataObjectType getObjectType() { + return DataObjectType.SNAPSHOT; + } + + @Override + public DataStoreTO getDataStore() { + return this.dataStore; + } + + public void setDataStore(DataStoreTO store) { + this.dataStore = store; + } + + @Override + public String getPath() { + return this.path; + } + + public void setPath(String path) { + this.path = path; + } + + public Long getPhysicalSize() { + return this.physicalSize; + } + + public void setPhysicalSize(Long physicalSize ) { + this.physicalSize = physicalSize; + } + + public VolumeObjectTO getVolume() { + return volume; + } + + public void setVolume(VolumeObjectTO volume) { + this.volume = volume; + } + + public String getParentSnapshotPath() { + return parentSnapshotPath; + } + + public void setParentSnapshotPath(String parentSnapshotPath) { + this.parentSnapshotPath = parentSnapshotPath; + } + + public String getVmName() { + return vmName; + } + + public void setVmName(String vmName) { + this.vmName = vmName; + } + + @Override + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public HypervisorType getHypervisorType() { + return hypervisorType; + } + + public void setHypervisorType(HypervisorType hypervisorType) { + this.hypervisorType = hypervisorType; + } + + public boolean getquiescevm() { + return this.quiescevm; + } + + public void setQuiescevm(boolean quiescevm) { + this.quiescevm = quiescevm; + } + + public String[] getParents() { + return parents; + } + + @Override + public String toString() { + return new StringBuilder("SnapshotTO[datastore=").append(dataStore).append("|volume=").append(volume).append("|path").append(path).append("]").toString(); + } +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/to/TemplateObjectTO.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/to/TemplateObjectTO.java new file mode 100644 index 0000000000..71f19e389d --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/to/TemplateObjectTO.java @@ -0,0 +1,222 @@ +// +// 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 org.apache.cloudstack.storage.to; + +import com.cloud.agent.api.to.DataObjectType; +import com.cloud.agent.api.to.DataStoreTO; +import com.cloud.agent.api.to.DataTO; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.storage.Storage.ImageFormat; +import com.cloud.template.VirtualMachineTemplate; + +import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo; + +public class TemplateObjectTO implements DataTO { + private String path; + private String origUrl; + private String uuid; + private long id; + private ImageFormat format; + private long accountId; + private String checksum; + private boolean hvm; + private String displayText; + private DataStoreTO imageDataStore; + private String name; + private String guestOsType; + private Long size; + private Long physicalSize; + private Hypervisor.HypervisorType hypervisorType; + + public TemplateObjectTO() { + + } + + public TemplateObjectTO(VirtualMachineTemplate template) { + this.uuid = template.getUuid(); + this.id = template.getId(); + this.origUrl = template.getUrl(); + this.displayText = template.getDisplayText(); + this.checksum = template.getChecksum(); + this.hvm = template.isRequiresHvm(); + this.accountId = template.getAccountId(); + this.name = template.getUniqueName(); + this.format = template.getFormat(); + this.hypervisorType = template.getHypervisorType(); + } + + public TemplateObjectTO(TemplateInfo template) { + this.path = template.getInstallPath(); + this.uuid = template.getUuid(); + this.id = template.getId(); + this.origUrl = template.getUrl(); + this.displayText = template.getDisplayText(); + this.checksum = template.getChecksum(); + this.hvm = template.isRequiresHvm(); + this.accountId = template.getAccountId(); + this.name = template.getUniqueName(); + this.format = template.getFormat(); + if (template.getDataStore() != null) { + this.imageDataStore = template.getDataStore().getTO(); + } + this.hypervisorType = template.getHypervisorType(); + } + + @Override + public String getPath() { + return this.path; + } + + public String getUuid() { + return this.uuid; + } + + @Override + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public ImageFormat getFormat() { + return format; + } + + public long getAccountId() { + return accountId; + } + + public String getChecksum() { + return checksum; + } + + public boolean isRequiresHvm() { + return hvm; + } + + public void setRequiresHvm(boolean hvm) { + this.hvm = hvm; + } + + public String getDescription() { + return displayText; + } + + public void setDescription(String desc) { + this.displayText = desc; + } + + @Override + public DataObjectType getObjectType() { + return DataObjectType.TEMPLATE; + } + + @Override + public DataStoreTO getDataStore() { + return this.imageDataStore; + } + + public void setHypervisorType(Hypervisor.HypervisorType hypervisorType) { + this.hypervisorType = hypervisorType; + } + + @Override + public Hypervisor.HypervisorType getHypervisorType() { + return this.hypervisorType; + } + + public void setDataStore(DataStoreTO store) { + this.imageDataStore = store; + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + public void setPath(String path) { + this.path = path; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public void setName(String name) { + this.name = name; + } + + public String getOrigUrl() { + return origUrl; + } + + public void setOrigUrl(String origUrl) { + this.origUrl = origUrl; + } + + public void setFormat(ImageFormat format) { + this.format = format; + } + + public void setAccountId(long accountId) { + this.accountId = accountId; + } + + public void setChecksum(String checksum) { + this.checksum = checksum; + } + + public void setImageDataStore(DataStoreTO imageDataStore) { + this.imageDataStore = imageDataStore; + } + + public String getGuestOsType() { + return guestOsType; + } + + public void setGuestOsType(String guestOsType) { + this.guestOsType = guestOsType; + } + + public Long getSize() { + return size; + } + + public void setSize(Long size) { + this.size = size; + } + + public Long getPhysicalSize() { + return physicalSize; + } + + public void setPhysicalSize(Long physicalSize) { + this.physicalSize = physicalSize; + } + + @Override + public String toString() { + return new StringBuilder("TemplateTO[id=").append(id).append("|origUrl=").append(origUrl).append("|name").append(name).append("]").toString(); + } +} diff --git a/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/to/VolumeObjectTO.java b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/to/VolumeObjectTO.java new file mode 100644 index 0000000000..038b48af6d --- /dev/null +++ b/cosmic-core/nucleo/src/main/java/org/apache/cloudstack/storage/to/VolumeObjectTO.java @@ -0,0 +1,255 @@ +// +// 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 org.apache.cloudstack.storage.to; + +import com.cloud.agent.api.to.DataObjectType; +import com.cloud.agent.api.to.DataStoreTO; +import com.cloud.agent.api.to.DataTO; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.offering.DiskOffering.DiskCacheMode; +import com.cloud.storage.Storage; +import com.cloud.storage.Volume; + +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; + +public class VolumeObjectTO implements DataTO { + private String uuid; + private Volume.Type volumeType; + private DataStoreTO dataStore; + private String name; + private Long size; + private String path; + private Long volumeId; + private String vmName; + private long accountId; + private String chainInfo; + private Storage.ImageFormat format; + private Storage.ProvisioningType provisioningType; + private long id; + + private Long deviceId; + private Long bytesReadRate; + private Long bytesWriteRate; + private Long iopsReadRate; + private Long iopsWriteRate; + private DiskCacheMode cacheMode; + private Hypervisor.HypervisorType hypervisorType; + + public VolumeObjectTO() { + + } + + public VolumeObjectTO(VolumeInfo volume) { + uuid = volume.getUuid(); + path = volume.getPath(); + accountId = volume.getAccountId(); + if (volume.getDataStore() != null) { + dataStore = volume.getDataStore().getTO(); + } else { + dataStore = null; + } + vmName = volume.getAttachedVmName(); + size = volume.getSize(); + setVolumeId(volume.getId()); + chainInfo = volume.getChainInfo(); + volumeType = volume.getVolumeType(); + name = volume.getName(); + setId(volume.getId()); + format = volume.getFormat(); + provisioningType = volume.getProvisioningType(); + bytesReadRate = volume.getBytesReadRate(); + bytesWriteRate = volume.getBytesWriteRate(); + iopsReadRate = volume.getIopsReadRate(); + iopsWriteRate = volume.getIopsWriteRate(); + cacheMode = volume.getCacheMode(); + hypervisorType = volume.getHypervisorType(); + setDeviceId(volume.getDeviceId()); + } + + public String getUuid() { + return uuid; + } + + @Override + public String getPath() { + return path; + } + + public Volume.Type getVolumeType() { + return volumeType; + } + + @Override + public DataStoreTO getDataStore() { + return dataStore; + } + + @Override + public Hypervisor.HypervisorType getHypervisorType() { + return hypervisorType; + } + + public void setDataStore(DataStoreTO store) { + dataStore = store; + } + + public void setDataStore(PrimaryDataStoreTO dataStore) { + this.dataStore = dataStore; + } + + public String getName() { + return name; + } + + public Long getSize() { + return size; + } + + @Override + public DataObjectType getObjectType() { + return DataObjectType.VOLUME; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public void setName(String name) { + this.name = name; + } + + public void setSize(long size) { + this.size = size; + } + + public void setPath(String path) { + this.path = path; + } + + public Long getVolumeId() { + return volumeId; + } + + public void setVolumeId(Long volumeId) { + this.volumeId = volumeId; + } + + public long getAccountId() { + return accountId; + } + + public void setAccountId(long accountId) { + this.accountId = accountId; + } + + public String getVmName() { + return vmName; + } + + public void setVmName(String vmName) { + this.vmName = vmName; + } + + public String getChainInfo() { + return chainInfo; + } + + public void setChainInfo(String chainInfo) { + this.chainInfo = chainInfo; + } + + @Override + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public Storage.ImageFormat getFormat() { + return format; + } + + public void setFormat(Storage.ImageFormat format) { + this.format = format; + } + + public Storage.ProvisioningType getProvisioningType(){ + return provisioningType; + } + + public void setProvisioningType(Storage.ProvisioningType provisioningType){ + this.provisioningType = provisioningType; + } + + @Override + public String toString() { + return new StringBuilder("volumeTO[uuid=").append(uuid).append("|path=").append(path).append("|datastore=").append(dataStore).append("]").toString(); + } + + public void setBytesReadRate(Long bytesReadRate) { + this.bytesReadRate = bytesReadRate; + } + + public Long getBytesReadRate() { + return bytesReadRate; + } + + public void setBytesWriteRate(Long bytesWriteRate) { + this.bytesWriteRate = bytesWriteRate; + } + + public Long getBytesWriteRate() { + return bytesWriteRate; + } + + public void setIopsReadRate(Long iopsReadRate) { + this.iopsReadRate = iopsReadRate; + } + + public Long getIopsReadRate() { + return iopsReadRate; + } + + public void setIopsWriteRate(Long iopsWriteRate) { + this.iopsWriteRate = iopsWriteRate; + } + + public Long getIopsWriteRate() { + return iopsWriteRate; + } + + public Long getDeviceId() { + return deviceId; + } + + public void setDeviceId(Long deviceId) { + this.deviceId = deviceId; + } + + public void setCacheMode(DiskCacheMode cacheMode) { + this.cacheMode = cacheMode; + } + + public DiskCacheMode getCacheMode() { + return cacheMode; + } +} diff --git a/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/allocator/module.properties b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/allocator/module.properties new file mode 100644 index 0000000000..b99b9af5dc --- /dev/null +++ b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/allocator/module.properties @@ -0,0 +1,21 @@ +# +# 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. +# + +name=allocator +parent=core diff --git a/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/allocator/spring-core-allocator-context.xml b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/allocator/spring-core-allocator-context.xml new file mode 100644 index 0000000000..700b9060d5 --- /dev/null +++ b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/allocator/spring-core-allocator-context.xml @@ -0,0 +1,34 @@ + + + + + + \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/allocator/spring-core-lifecycle-allocator-context-inheritable.xml b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/allocator/spring-core-lifecycle-allocator-context-inheritable.xml new file mode 100644 index 0000000000..e6ed961bbd --- /dev/null +++ b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/allocator/spring-core-lifecycle-allocator-context-inheritable.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/api/module.properties b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/api/module.properties new file mode 100644 index 0000000000..727b075f61 --- /dev/null +++ b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/api/module.properties @@ -0,0 +1,21 @@ +# +# 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. +# + +name=api +parent=core diff --git a/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/api/spring-core-lifecycle-api-context-inheritable.xml b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/api/spring-core-lifecycle-api-context-inheritable.xml new file mode 100644 index 0000000000..f1566b1302 --- /dev/null +++ b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/api/spring-core-lifecycle-api-context-inheritable.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/backend/module.properties b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/backend/module.properties new file mode 100644 index 0000000000..c4e29afcbe --- /dev/null +++ b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/backend/module.properties @@ -0,0 +1,21 @@ +# +# 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. +# + +name=backend +parent=core diff --git a/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/bootstrap/module.properties b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/bootstrap/module.properties new file mode 100644 index 0000000000..15532c44fa --- /dev/null +++ b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/bootstrap/module.properties @@ -0,0 +1,20 @@ +# +# 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. +# + +name=bootstrap diff --git a/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/bootstrap/spring-bootstrap-context-inheritable.xml b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/bootstrap/spring-bootstrap-context-inheritable.xml new file mode 100644 index 0000000000..84c301caeb --- /dev/null +++ b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/bootstrap/spring-bootstrap-context-inheritable.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + diff --git a/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/bootstrap/spring-bootstrap-context.xml b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/bootstrap/spring-bootstrap-context.xml new file mode 100644 index 0000000000..7a586a24c0 --- /dev/null +++ b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/bootstrap/spring-bootstrap-context.xml @@ -0,0 +1,35 @@ + + + + + + + diff --git a/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/compute/module.properties b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/compute/module.properties new file mode 100644 index 0000000000..3549c985f1 --- /dev/null +++ b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/compute/module.properties @@ -0,0 +1,21 @@ +# +# 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. +# + +name=compute +parent=backend diff --git a/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/compute/spring-core-lifecycle-compute-context-inheritable.xml b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/compute/spring-core-lifecycle-compute-context-inheritable.xml new file mode 100644 index 0000000000..f757e7ed09 --- /dev/null +++ b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/compute/spring-core-lifecycle-compute-context-inheritable.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + diff --git a/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/core/module.properties b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/core/module.properties new file mode 100644 index 0000000000..6ccd650264 --- /dev/null +++ b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/core/module.properties @@ -0,0 +1,21 @@ +# +# 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. +# + +name=core +parent=system diff --git a/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/core/spring-core-context.xml b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/core/spring-core-context.xml new file mode 100644 index 0000000000..242f72c127 --- /dev/null +++ b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/core/spring-core-context.xml @@ -0,0 +1,38 @@ + + + + + + + + + + diff --git a/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/core/spring-core-lifecycle-core-context-inheritable.xml b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/core/spring-core-lifecycle-core-context-inheritable.xml new file mode 100644 index 0000000000..515249dcb6 --- /dev/null +++ b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/core/spring-core-lifecycle-core-context-inheritable.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml new file mode 100644 index 0000000000..7c5823b951 --- /dev/null +++ b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/discoverer/module.properties b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/discoverer/module.properties new file mode 100644 index 0000000000..6af75afc4e --- /dev/null +++ b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/discoverer/module.properties @@ -0,0 +1,21 @@ +# +# 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. +# + +name=discoverer +parent=core diff --git a/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/discoverer/spring-core-lifecycle-discoverer-context-inheritable.xml b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/discoverer/spring-core-lifecycle-discoverer-context-inheritable.xml new file mode 100644 index 0000000000..640d17e98e --- /dev/null +++ b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/discoverer/spring-core-lifecycle-discoverer-context-inheritable.xml @@ -0,0 +1,37 @@ + + + + + + + + + diff --git a/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/network/module.properties b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/network/module.properties new file mode 100644 index 0000000000..5d2cc4457e --- /dev/null +++ b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/network/module.properties @@ -0,0 +1,21 @@ +# +# 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. +# + +name=network +parent=backend diff --git a/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/network/spring-core-lifecycle-network-context-inheritable.xml b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/network/spring-core-lifecycle-network-context-inheritable.xml new file mode 100644 index 0000000000..1986777f2d --- /dev/null +++ b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/network/spring-core-lifecycle-network-context-inheritable.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/planner/module.properties b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/planner/module.properties new file mode 100644 index 0000000000..26c61d9e8e --- /dev/null +++ b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/planner/module.properties @@ -0,0 +1,21 @@ +# +# 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. +# + +name=planner +parent=allocator \ No newline at end of file diff --git a/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/planner/spring-core-lifecycle-planner-context-inheritable.xml b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/planner/spring-core-lifecycle-planner-context-inheritable.xml new file mode 100644 index 0000000000..2548308f21 --- /dev/null +++ b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/planner/spring-core-lifecycle-planner-context-inheritable.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + diff --git a/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/storage/module.properties b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/storage/module.properties new file mode 100644 index 0000000000..f35ed85697 --- /dev/null +++ b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/storage/module.properties @@ -0,0 +1,21 @@ +# +# 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. +# + +name=storage +parent=backend diff --git a/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/storage/spring-lifecycle-storage-context-inheritable.xml b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/storage/spring-lifecycle-storage-context-inheritable.xml new file mode 100644 index 0000000000..7b86e935ab --- /dev/null +++ b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/storage/spring-lifecycle-storage-context-inheritable.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/system/module.properties b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/system/module.properties new file mode 100644 index 0000000000..cc2dabccc5 --- /dev/null +++ b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/system/module.properties @@ -0,0 +1,21 @@ +# +# 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. +# + +name=system +parent=bootstrap diff --git a/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/system/spring-core-system-context-inheritable.xml b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/system/spring-core-system-context-inheritable.xml new file mode 100644 index 0000000000..2d454acdd7 --- /dev/null +++ b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/system/spring-core-system-context-inheritable.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/system/spring-core-system-context.xml b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/system/spring-core-system-context.xml new file mode 100644 index 0000000000..96b627e9e2 --- /dev/null +++ b/cosmic-core/nucleo/src/main/resources/META-INF/cloudstack/system/spring-core-system-context.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/cosmic-core/nucleo/src/test/java/com/cloud/agent/api/routing/SetNetworkACLCommandTest.java b/cosmic-core/nucleo/src/test/java/com/cloud/agent/api/routing/SetNetworkACLCommandTest.java new file mode 100644 index 0000000000..9b04c92035 --- /dev/null +++ b/cosmic-core/nucleo/src/test/java/com/cloud/agent/api/routing/SetNetworkACLCommandTest.java @@ -0,0 +1,53 @@ +// +// 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.agent.api.routing; + +import static org.junit.Assert.assertEquals; + +import java.util.List; + +import com.cloud.agent.api.to.NetworkACLTO; +import com.google.common.collect.Lists; + +import org.junit.Test; + +public class SetNetworkACLCommandTest { + + @Test + public void testNetworkAclRuleOrdering(){ + + //given + List aclList = Lists.newArrayList(); + + aclList.add(new NetworkACLTO(3, null, null, null, null, false, false, null, null, null, null, false, 3)); + aclList.add(new NetworkACLTO(1, null, null, null, null, false, false, null, null, null, null, false, 1)); + aclList.add(new NetworkACLTO(2, null, null, null, null, false, false, null, null, null, null, false, 2)); + + SetNetworkACLCommand cmd = new SetNetworkACLCommand(aclList, null); + + //when + cmd.orderNetworkAclRulesByRuleNumber(aclList); + + //then + for(int i=0; i< aclList.size();i++){ + assertEquals(aclList.get(i).getNumber(), i+1); + } + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/test/java/com/cloud/agent/resource/virtualnetwork/ConfigHelperTest.java b/cosmic-core/nucleo/src/test/java/com/cloud/agent/resource/virtualnetwork/ConfigHelperTest.java new file mode 100644 index 0000000000..2ac088817e --- /dev/null +++ b/cosmic-core/nucleo/src/test/java/com/cloud/agent/resource/virtualnetwork/ConfigHelperTest.java @@ -0,0 +1,285 @@ +// +// 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.agent.resource.virtualnetwork; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import com.cloud.agent.api.routing.DeleteIpAliasCommand; +import com.cloud.agent.api.routing.DnsMasqConfigCommand; +import com.cloud.agent.api.routing.IpAliasTO; +import com.cloud.agent.api.routing.IpAssocVpcCommand; +import com.cloud.agent.api.routing.LoadBalancerConfigCommand; +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.api.routing.SetPortForwardingRulesVpcCommand; +import com.cloud.agent.api.to.DhcpTO; +import com.cloud.agent.api.to.IpAddressTO; +import com.cloud.agent.api.to.LoadBalancerTO; +import com.cloud.agent.api.to.NicTO; +import com.cloud.agent.api.to.PortForwardingRuleTO; +import com.cloud.agent.resource.virtualnetwork.facade.AbstractConfigItemFacade; +import com.cloud.agent.resource.virtualnetwork.model.DhcpConfig; +import com.cloud.agent.resource.virtualnetwork.model.DhcpConfigEntry; +import com.cloud.agent.resource.virtualnetwork.model.ForwardingRule; +import com.cloud.agent.resource.virtualnetwork.model.ForwardingRules; +import com.cloud.agent.resource.virtualnetwork.model.IpAddress; +import com.cloud.agent.resource.virtualnetwork.model.IpAddressAlias; +import com.cloud.agent.resource.virtualnetwork.model.IpAliases; +import com.cloud.agent.resource.virtualnetwork.model.IpAssociation; +import com.cloud.agent.resource.virtualnetwork.model.LoadBalancerRule; +import com.cloud.agent.resource.virtualnetwork.model.LoadBalancerRules; +import com.cloud.network.lb.LoadBalancingRule.LbDestination; +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import org.junit.Test; + +public class ConfigHelperTest { + + private final static Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); + + private final String ROUTERNAME = "r-4-VM"; + + @Test + public void testGenerateCommandCfgLoadBalancer() { + + final LoadBalancerConfigCommand command = generateLoadBalancerConfigCommand(); + + final AbstractConfigItemFacade configItemFacade = AbstractConfigItemFacade.getInstance(command.getClass()); + + final List config = configItemFacade.generateConfig(command); + assertTrue(config.size() > 0); + + final ConfigItem fileConfig = config.get(0); + assertNotNull(fileConfig); + assertTrue(fileConfig instanceof FileConfigItem); + + final String fileContents = ((FileConfigItem)fileConfig).getFileContents(); + assertNotNull(fileContents); + + final LoadBalancerRules jsonClass = gson.fromJson(fileContents, LoadBalancerRules.class); + assertNotNull(jsonClass); + assertEquals(jsonClass.getType(), "loadbalancer"); + + final List rules = jsonClass.getRules(); + assertNotNull(rules); + assertTrue(rules.size() == 1); + assertEquals(rules.get(0).getRouterIp(), "10.1.10.2"); + + final ConfigItem scriptConfig = config.get(1); + assertNotNull(scriptConfig); + assertTrue(scriptConfig instanceof ScriptConfigItem); + } + + @Test + public void testSetPortForwardingRulesVpc() { + + final SetPortForwardingRulesVpcCommand command = generateSetPortForwardingRulesVpcCommand(); + + final AbstractConfigItemFacade configItemFacade = AbstractConfigItemFacade.getInstance(command.getClass()); + + final List config = configItemFacade.generateConfig(command); + assertTrue(config.size() > 0); + + final ConfigItem fileConfig = config.get(0); + assertNotNull(fileConfig); + assertTrue(fileConfig instanceof FileConfigItem); + + final String fileContents = ((FileConfigItem)fileConfig).getFileContents(); + assertNotNull(fileContents); + + final ForwardingRules jsonClass = gson.fromJson(fileContents, ForwardingRules.class); + assertNotNull(jsonClass); + assertEquals(jsonClass.getType(), "forwardrules"); + + final ForwardingRule [] rules = jsonClass.getRules(); + assertNotNull(rules); + assertTrue(rules.length == 2); + assertEquals(rules[0].getSourceIpAddress(), "64.1.1.10"); + + final ConfigItem scriptConfig = config.get(1); + assertNotNull(scriptConfig); + assertTrue(scriptConfig instanceof ScriptConfigItem); + } + + @Test + public void testIpAssocVpc() { + + final IpAssocVpcCommand command = generateIpAssocVpcCommand(); + + final AbstractConfigItemFacade configItemFacade = AbstractConfigItemFacade.getInstance(command.getClass()); + + final List config = configItemFacade.generateConfig(command); + assertTrue(config.size() > 0); + + final ConfigItem fileConfig = config.get(0); + assertNotNull(fileConfig); + assertTrue(fileConfig instanceof FileConfigItem); + + final String fileContents = ((FileConfigItem)fileConfig).getFileContents(); + assertNotNull(fileContents); + + final IpAssociation jsonClass = gson.fromJson(fileContents, IpAssociation.class); + assertNotNull(jsonClass); + assertEquals(jsonClass.getType(), "ips"); + + final IpAddress [] ips = jsonClass.getIpAddress(); + assertNotNull(ips); + assertTrue(ips.length == 3); + assertEquals(ips[0].getPublicIp(), "64.1.1.10"); + + final ConfigItem scriptConfig = config.get(1); + assertNotNull(scriptConfig); + assertTrue(scriptConfig instanceof ScriptConfigItem); + } + + @Test + public void testDnsMasqConfig() { + + final DnsMasqConfigCommand command = generateDnsMasqConfigCommand(); + + final AbstractConfigItemFacade configItemFacade = AbstractConfigItemFacade.getInstance(command.getClass()); + + final List config = configItemFacade.generateConfig(command); + assertTrue(config.size() > 0); + + final ConfigItem fileConfig = config.get(0); + assertNotNull(fileConfig); + assertTrue(fileConfig instanceof FileConfigItem); + + final String fileContents = ((FileConfigItem)fileConfig).getFileContents(); + assertNotNull(fileContents); + + final DhcpConfig jsonClass = gson.fromJson(fileContents, DhcpConfig.class); + assertNotNull(jsonClass); + assertEquals(jsonClass.getType(), "dhcpconfig"); + + final List entries = jsonClass.getEntries(); + assertNotNull(entries); + assertTrue(entries.size() == 2); + assertEquals(entries.get(0).getRouterIpAddress(), "10.1.20.2"); + + final ConfigItem scriptConfig = config.get(1); + assertNotNull(scriptConfig); + assertTrue(scriptConfig instanceof ScriptConfigItem); + } + + @Test + public void testDeleteIpAlias() { + + final DeleteIpAliasCommand command = generateDeleteIpAliasCommand(); + + final AbstractConfigItemFacade configItemFacade = AbstractConfigItemFacade.getInstance(command.getClass()); + + final List config = configItemFacade.generateConfig(command); + assertTrue(config.size() > 0); + + final ConfigItem fileConfig = config.get(0); + assertNotNull(fileConfig); + assertTrue(fileConfig instanceof FileConfigItem); + + final String fileContents = ((FileConfigItem)fileConfig).getFileContents(); + assertNotNull(fileContents); + + final IpAliases jsonClass = gson.fromJson(fileContents, IpAliases.class); + assertNotNull(jsonClass); + assertEquals(jsonClass.getType(), "ipaliases"); + + final List aliases = jsonClass.getAliases(); + assertNotNull(aliases); + assertTrue(aliases.size() == 6); + assertEquals(aliases.get(0).getIpAddress(), "169.254.3.10"); + + final ConfigItem scriptConfig = config.get(1); + assertNotNull(scriptConfig); + assertTrue(scriptConfig instanceof ScriptConfigItem); + } + + protected LoadBalancerConfigCommand generateLoadBalancerConfigCommand() { + final List lbs = new ArrayList<>(); + final List dests = new ArrayList<>(); + dests.add(new LbDestination(80, 8080, "10.1.10.2", false)); + dests.add(new LbDestination(80, 8080, "10.1.10.2", true)); + lbs.add(new LoadBalancerTO(UUID.randomUUID().toString(), "64.10.1.10", 80, "tcp", "algo", false, false, false, dests)); + + final LoadBalancerTO[] arrayLbs = new LoadBalancerTO[lbs.size()]; + lbs.toArray(arrayLbs); + + final NicTO nic = new NicTO(); + final LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(arrayLbs, "64.10.2.10", "10.1.10.2", "192.168.1.2", nic, null, "1000", false); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_IP, "10.1.10.2"); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + + return cmd; + } + + protected SetPortForwardingRulesVpcCommand generateSetPortForwardingRulesVpcCommand() { + final List pfRules = new ArrayList<>(); + pfRules.add(new PortForwardingRuleTO(1, "64.1.1.10", 22, 80, "10.10.1.10", 22, 80, "TCP", false, false)); + pfRules.add(new PortForwardingRuleTO(2, "64.1.1.11", 8080, 8080, "10.10.1.11", 8080, 8080, "UDP", true, false)); + + final SetPortForwardingRulesVpcCommand cmd = new SetPortForwardingRulesVpcCommand(pfRules); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + assertEquals(cmd.getAnswersCount(), 2); + + return cmd; + } + + protected DnsMasqConfigCommand generateDnsMasqConfigCommand() { + final List dhcps = new ArrayList<>(); + dhcps.add(new DhcpTO("10.1.20.2", "10.1.20.1", "255.255.255.0", "10.1.20.5")); + dhcps.add(new DhcpTO("10.1.21.2", "10.1.21.1", "255.255.255.0", "10.1.21.5")); + + final DnsMasqConfigCommand cmd = new DnsMasqConfigCommand(dhcps); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + return cmd; + } + + protected DeleteIpAliasCommand generateDeleteIpAliasCommand() { + final List aliases = new ArrayList<>(); + aliases.add(new IpAliasTO("169.254.3.10", "255.255.255.0", "1")); + aliases.add(new IpAliasTO("169.254.3.11", "255.255.255.0", "2")); + aliases.add(new IpAliasTO("169.254.3.12", "255.255.255.0", "3")); + + final DeleteIpAliasCommand cmd = new DeleteIpAliasCommand("169.254.10.1", aliases, aliases); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + return cmd; + } + + protected IpAssocVpcCommand generateIpAssocVpcCommand() { + final List ips = new ArrayList(); + ips.add(new IpAddressTO(1, "64.1.1.10", true, true, true, "vlan://64", "64.1.1.1", "255.255.255.0", "01:23:45:67:89:AB", 1000, false)); + ips.add(new IpAddressTO(2, "64.1.1.11", false, false, true, "vlan://64", "64.1.1.1", "255.255.255.0", "01:23:45:67:89:AB", 1000, false)); + ips.add(new IpAddressTO(3, "65.1.1.11", true, false, false, "vlan://65", "65.1.1.1", "255.255.255.0", "11:23:45:67:89:AB", 1000, false)); + + final IpAddressTO[] ipArray = ips.toArray(new IpAddressTO[ips.size()]); + final IpAssocVpcCommand cmd = new IpAssocVpcCommand(ipArray); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + assertEquals(6, cmd.getAnswersCount()); // AnswersCount is clearly wrong as it doesn't know enough to tell + + return cmd; + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/test/java/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResourceTest.java b/cosmic-core/nucleo/src/test/java/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResourceTest.java new file mode 100644 index 0000000000..2a884fcb78 --- /dev/null +++ b/cosmic-core/nucleo/src/test/java/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResourceTest.java @@ -0,0 +1,919 @@ +// +// 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.agent.resource.virtualnetwork; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; + +import javax.naming.ConfigurationException; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.BumpUpPriorityCommand; +import com.cloud.agent.api.SetupGuestNetworkCommand; +import com.cloud.agent.api.routing.AggregationControlCommand; +import com.cloud.agent.api.routing.AggregationControlCommand.Action; +import com.cloud.agent.api.routing.CreateIpAliasCommand; +import com.cloud.agent.api.routing.DeleteIpAliasCommand; +import com.cloud.agent.api.routing.DhcpEntryCommand; +import com.cloud.agent.api.routing.DnsMasqConfigCommand; +import com.cloud.agent.api.routing.GroupAnswer; +import com.cloud.agent.api.routing.IpAliasTO; +import com.cloud.agent.api.routing.IpAssocCommand; +import com.cloud.agent.api.routing.IpAssocVpcCommand; +import com.cloud.agent.api.routing.LoadBalancerConfigCommand; +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.api.routing.RemoteAccessVpnCfgCommand; +import com.cloud.agent.api.routing.SavePasswordCommand; +import com.cloud.agent.api.routing.SetFirewallRulesCommand; +import com.cloud.agent.api.routing.SetMonitorServiceCommand; +import com.cloud.agent.api.routing.SetNetworkACLCommand; +import com.cloud.agent.api.routing.SetPortForwardingRulesCommand; +import com.cloud.agent.api.routing.SetPortForwardingRulesVpcCommand; +import com.cloud.agent.api.routing.SetSourceNatCommand; +import com.cloud.agent.api.routing.SetStaticNatRulesCommand; +import com.cloud.agent.api.routing.SetStaticRouteCommand; +import com.cloud.agent.api.routing.Site2SiteVpnCfgCommand; +import com.cloud.agent.api.routing.VmDataCommand; +import com.cloud.agent.api.routing.VpnUsersCfgCommand; +import com.cloud.agent.api.to.DhcpTO; +import com.cloud.agent.api.to.FirewallRuleTO; +import com.cloud.agent.api.to.IpAddressTO; +import com.cloud.agent.api.to.LoadBalancerTO; +import com.cloud.agent.api.to.MonitorServiceTO; +import com.cloud.agent.api.to.NetworkACLTO; +import com.cloud.agent.api.to.NicTO; +import com.cloud.agent.api.to.PortForwardingRuleTO; +import com.cloud.network.lb.LoadBalancingRule.LbDestination; +import com.cloud.network.rules.FirewallRule.Purpose; +import com.cloud.network.vpc.NetworkACLItem.TrafficType; +import com.cloud.network.vpc.VpcGateway; +import com.cloud.utils.ExecutionResult; +import com.cloud.utils.net.NetUtils; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(loader = AnnotationConfigContextLoader.class) +@Ignore("Just forget until the rewrite is a little more done") +public class VirtualRoutingResourceTest implements VirtualRouterDeployer { + VirtualRoutingResource _resource; + NetworkElementCommand _currentCmd; + int _count; + String _file; + + String ROUTERIP = "169.254.3.4"; + String ROUTERGUESTIP = "10.200.1.1"; + String ROUTERNAME = "r-4-VM"; + + @Override + public ExecutionResult executeInVR(final String routerIp, final String script, final String args) { + return executeInVR(routerIp, script, args, 60); + } + + @Override + public ExecutionResult executeInVR(final String routerIp, final String script, final String args, final int timeout) { + assertEquals(routerIp, ROUTERIP); + verifyCommand(_currentCmd, script, args); + return new ExecutionResult(true, null); + } + + @Override + public ExecutionResult createFileInVR(final String routerIp, final String path, final String filename, final String content) { + assertEquals(routerIp, ROUTERIP); + verifyFile(_currentCmd, path, filename, content); + return new ExecutionResult(true, null); + } + + @Override + public ExecutionResult prepareCommand(final NetworkElementCommand cmd) { + cmd.setRouterAccessIp(ROUTERIP); + _currentCmd = cmd; + if (cmd instanceof IpAssocVpcCommand) { + return prepareNetworkElementCommand((IpAssocVpcCommand)cmd); + } else if (cmd instanceof IpAssocCommand) { + return prepareNetworkElementCommand((IpAssocCommand)cmd); + } else if (cmd instanceof SetupGuestNetworkCommand) { + return prepareNetworkElementCommand((SetupGuestNetworkCommand)cmd); + } else if (cmd instanceof SetSourceNatCommand) { + return prepareNetworkElementCommand((SetSourceNatCommand)cmd); + } else if (cmd instanceof SetNetworkACLCommand) { + return prepareNetworkElementCommand((SetNetworkACLCommand)cmd); + } + return new ExecutionResult(true, null); + } + + @Override + public ExecutionResult cleanupCommand(final NetworkElementCommand cmd) { + return new ExecutionResult(true, null); + } + + @Before + public void setup() { + _resource = new VirtualRoutingResource(this); + try { + _resource.configure("VRResource", new HashMap()); + } catch (final ConfigurationException e) { + e.printStackTrace(); + } + } + + private void verifyFile(final NetworkElementCommand cmd, final String path, final String filename, final String content) { + if (cmd instanceof AggregationControlCommand) { + verifyFile(cmd, path, filename, content); + } else if (cmd instanceof LoadBalancerConfigCommand) { + verifyFile((LoadBalancerConfigCommand)cmd, path, filename, content); + } + } + + protected void verifyCommand(final NetworkElementCommand cmd, final String script, final String args) { + if (cmd instanceof SetStaticRouteCommand) { + verifyArgs((SetStaticRouteCommand) cmd, script, args); + } else if (cmd instanceof SetStaticNatRulesCommand) { + verifyArgs((SetStaticNatRulesCommand) cmd, script, args); + } else if (cmd instanceof LoadBalancerConfigCommand) { + verifyArgs((LoadBalancerConfigCommand) cmd, script, args); + } else if (cmd instanceof SavePasswordCommand) { + verifyArgs((SavePasswordCommand)cmd, script, args); + } else if (cmd instanceof DhcpEntryCommand) { + verifyArgs((DhcpEntryCommand)cmd, script, args); + } else if (cmd instanceof DnsMasqConfigCommand) { + verifyArgs((DnsMasqConfigCommand)cmd, script, args); + } else if (cmd instanceof VmDataCommand) { + verifyArgs((VmDataCommand)cmd, script, args); + } else if (cmd instanceof RemoteAccessVpnCfgCommand) { + verifyArgs((RemoteAccessVpnCfgCommand)cmd, script, args); + } else if (cmd instanceof VpnUsersCfgCommand) { + verifyArgs((VpnUsersCfgCommand)cmd, script, args); + } else if (cmd instanceof Site2SiteVpnCfgCommand) { + verifyArgs((Site2SiteVpnCfgCommand)cmd, script, args); + } else if (cmd instanceof SetMonitorServiceCommand) { + verifyArgs((SetMonitorServiceCommand)cmd, script, args); + } else if (cmd instanceof SetupGuestNetworkCommand) { + verifyArgs((SetupGuestNetworkCommand)cmd, script, args); + } else if (cmd instanceof SetNetworkACLCommand) { + verifyArgs((SetNetworkACLCommand)cmd, script, args); + } else if (cmd instanceof SetSourceNatCommand) { + verifyArgs((SetSourceNatCommand)cmd, script, args); + } else if (cmd instanceof IpAssocCommand) { + verifyArgs((IpAssocCommand)cmd, script, args); + } + + if (cmd instanceof AggregationControlCommand) { + verifyArgs((AggregationControlCommand)cmd, script, args); + } + } + + private void verifyArgs(final VpnUsersCfgCommand cmd, final String script, final String args) { + //To change body of created methods use File | Settings | File Templates. + } + + private void verifyArgs(final SetStaticRouteCommand cmd, final String script, final String args) { + //To change body of created methods use File | Settings | File Templates. + } + + private void verifyArgs(final SetStaticNatRulesCommand cmd, final String script, final String args) { + //To change body of created methods use File | Settings | File Templates. + } + + @Test + public void testBumpUpCommand() { + final BumpUpPriorityCommand cmd = new BumpUpPriorityCommand(); + final Answer answer = _resource.executeRequest(cmd); + assertTrue(answer.getResult()); + } + + @Test + public void testSetPortForwardingRulesVpcCommand() { + final SetPortForwardingRulesVpcCommand cmd = generateSetPortForwardingRulesVpcCommand(); + + // Reset rule check count + _count = 0; + + final Answer answer = _resource.executeRequest(cmd); + assertTrue(answer instanceof GroupAnswer); + assertEquals(((GroupAnswer) answer).getResults().length, 2); + assertTrue(answer.getResult()); + } + + protected SetPortForwardingRulesVpcCommand generateSetPortForwardingRulesVpcCommand() { + final List pfRules = new ArrayList<>(); + pfRules.add(new PortForwardingRuleTO(1, "64.1.1.10", 22, 80, "10.10.1.10", 22, 80, "TCP", false, false)); + pfRules.add(new PortForwardingRuleTO(2, "64.1.1.11", 8080, 8080, "10.10.1.11", 8080, 8080, "UDP", true, false)); + final SetPortForwardingRulesVpcCommand cmd = new SetPortForwardingRulesVpcCommand(pfRules); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + assertEquals(cmd.getAnswersCount(), 2); + return cmd; + } + + @Test + public void testSetPortForwardingRulesCommand() { + final SetPortForwardingRulesCommand cmd = generateSetPortForwardingRulesCommand(); + // Reset rule check count + _count = 0; + + final Answer answer = _resource.executeRequest(cmd); + assertTrue(answer instanceof GroupAnswer); + assertEquals(((GroupAnswer) answer).getResults().length, 2); + assertTrue(answer.getResult()); + } + + protected SetPortForwardingRulesCommand generateSetPortForwardingRulesCommand() { + final List pfRules = new ArrayList<>(); + pfRules.add(new PortForwardingRuleTO(1, "64.1.1.10", 22, 80, "10.10.1.10", 22, 80, "TCP", false, false)); + pfRules.add(new PortForwardingRuleTO(2, "64.1.1.11", 8080, 8080, "10.10.1.11", 8080, 8080, "UDP", true, false)); + final SetPortForwardingRulesCommand cmd = new SetPortForwardingRulesCommand(pfRules); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + assertEquals(cmd.getAnswersCount(), 2); + return cmd; + } + + @Test + public void testIpAssocCommand() { + final IpAssocCommand cmd = generateIpAssocCommand(); + _count = 0; + + final Answer answer = _resource.executeRequest(cmd); + assertTrue(answer instanceof GroupAnswer); + assertEquals(2, ((GroupAnswer)answer).getResults().length); + assertTrue(answer.getResult()); + + } + + private ExecutionResult prepareNetworkElementCommand(final IpAssocCommand cmd) { + final IpAddressTO[] ips = cmd.getIpAddresses(); + for (final IpAddressTO ip : ips) { + ip.setNicDevId(2); + } + return new ExecutionResult(true, null); + } + + protected IpAssocCommand generateIpAssocCommand() { + final List ips = new ArrayList<>(); + ips.add(new IpAddressTO(1, "64.1.1.10", true, true, true, "vlan://64", "64.1.1.1", "255.255.255.0", "01:23:45:67:89:AB", 1000, false)); + ips.add(new IpAddressTO(2, "64.1.1.11", false, false, false, "vlan://64", "64.1.1.1", "255.255.255.0", "01:23:45:67:89:AB", 1000, false)); + ips.add(new IpAddressTO(3, "65.1.1.11", true, false, false, "vlan://65", "65.1.1.1", "255.255.255.0", "11:23:45:67:89:AB", 1000, false)); + final IpAddressTO[] ipArray = ips.toArray(new IpAddressTO[ips.size()]); + final IpAssocCommand cmd = new IpAssocCommand(ipArray); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + assertEquals(cmd.getAnswersCount(), 3); + + return cmd; + } + + @Test + public void testIpAssocVpcCommand() { + final IpAssocVpcCommand cmd = generateIpAssocVpcCommand(); + _count = 0; + + final Answer answer = _resource.executeRequest(cmd); + assertTrue(answer instanceof GroupAnswer); + assertEquals(2, ((GroupAnswer)answer).getResults().length); + assertTrue(answer.getResult()); + + } + + private ExecutionResult prepareNetworkElementCommand(final IpAssocVpcCommand cmd) { + final IpAddressTO[] ips = cmd.getIpAddresses(); + for (final IpAddressTO ip : ips) { + ip.setNicDevId(2); + } + return new ExecutionResult(true, null); + } + + protected IpAssocVpcCommand generateIpAssocVpcCommand() { + final List ips = new ArrayList(); + ips.add(new IpAddressTO(1, "64.1.1.10", true, true, true, "vlan://64", "64.1.1.1", "255.255.255.0", "01:23:45:67:89:AB", 1000, false)); + ips.add(new IpAddressTO(2, "64.1.1.11", false, false, true, "vlan://64", "64.1.1.1", "255.255.255.0", "01:23:45:67:89:AB", 1000, false)); + ips.add(new IpAddressTO(3, "65.1.1.11", true, false, false, "vlan://65", "65.1.1.1", "255.255.255.0", "11:23:45:67:89:AB", 1000, false)); + final IpAddressTO[] ipArray = ips.toArray(new IpAddressTO[ips.size()]); + final IpAssocVpcCommand cmd = new IpAssocVpcCommand(ipArray); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + assertEquals(6, cmd.getAnswersCount()); // AnswersCount is clearly wrong as it doesn't know enough to tell + + return cmd; + } + + private void verifyArgs(final IpAssocCommand cmd, final String script, final String args) { + if (cmd instanceof IpAssocVpcCommand) { + _count ++; + switch (_count) { + case 1: + assertEquals(VRScripts.UPDATE_CONFIG, script); + assertEquals(VRScripts.IP_ASSOCIATION_CONFIG, args); + break; + default: + fail("Failed to recongize the match!"); + } + } else { + assertEquals(script, VRScripts.UPDATE_CONFIG); + _count ++; + switch (_count) { + case 1: + assertEquals(VRScripts.IP_ASSOCIATION_CONFIG, args); + break; + case 2: + assertEquals(VRScripts.IP_ASSOCIATION_CONFIG, args); + break; + case 3: + assertEquals(VRScripts.IP_ASSOCIATION_CONFIG, args); + break; + default: + fail("Failed to recongize the match!"); + } + } + } + + @Test + public void testSourceNatCommand() { + final SetSourceNatCommand cmd = generateSetSourceNatCommand(); + final Answer answer = _resource.executeRequest(cmd); + assertTrue(answer.getResult()); + } + + private ExecutionResult prepareNetworkElementCommand(final SetSourceNatCommand cmd) { + final IpAddressTO ip = cmd.getIpAddress(); + ip.setNicDevId(1); + return new ExecutionResult(true, null); + } + + protected SetSourceNatCommand generateSetSourceNatCommand() { + final IpAddressTO ip = new IpAddressTO(1, "64.1.1.10", true, true, true, "vlan://64", "64.1.1.1", "255.255.255.0", "01:23:45:67:89:AB", 1000, false); + final SetSourceNatCommand cmd = new SetSourceNatCommand(ip, true); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + return cmd; + } + + private void verifyArgs(final SetSourceNatCommand cmd, final String script, final String args) { + assertEquals(script, VRScripts.VPC_SOURCE_NAT); + assertEquals(args, "-A -l 64.1.1.10 -c eth1"); + } + + @Test + public void testNetworkACLCommand() { + final SetNetworkACLCommand cmd = generateSetNetworkACLCommand(); + _count = 0; + + Answer answer = _resource.executeRequest(cmd); + assertTrue(answer.getResult()); + + cmd.setAccessDetail(NetworkElementCommand.VPC_PRIVATE_GATEWAY, String.valueOf(VpcGateway.Type.Private)); + answer = _resource.executeRequest(cmd); + assertTrue(answer.getResult()); + } + + protected SetNetworkACLCommand generateSetNetworkACLCommand() { + final List acls = new ArrayList<>(); + final List cidrs = new ArrayList<>(); + cidrs.add("192.168.0.1/24"); + cidrs.add("192.168.0.2/24"); + acls.add(new NetworkACLTO(1, "64", "TCP", 20, 80, false, false, cidrs, 0, 0, TrafficType.Ingress, true, 1)); + acls.add(new NetworkACLTO(2, "64", "ICMP", 0, 0, false, false, cidrs, -1, -1, TrafficType.Ingress, false, 2)); + acls.add(new NetworkACLTO(3, "65", "ALL", 0, 0, false, false, cidrs, -1, -1, TrafficType.Egress, true, 3)); + final NicTO nic = new NicTO(); + nic.setMac("01:23:45:67:89:AB"); + nic.setIp("192.168.1.1"); + nic.setNetmask("255.255.255.0"); + final SetNetworkACLCommand cmd = new SetNetworkACLCommand(acls, nic); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + + return cmd; + } + + private void verifyArgs(final SetNetworkACLCommand cmd, final String script, final String args) { + _count ++; + switch (_count) { + case 1: + // FIXME Check the json content + assertEquals(VRScripts.UPDATE_CONFIG, script); + assertEquals(VRScripts.NETWORK_ACL_CONFIG, args); + // assertEquals(args, " -d eth3 -M 01:23:45:67:89:AB -i 192.168.1.1 -m 24 -a Egress:ALL:0:0:192.168.0.1/24-192.168.0.2/24:ACCEPT:," + + // "Ingress:ICMP:0:0:192.168.0.1/24-192.168.0.2/24:DROP:,Ingress:TCP:20:80:192.168.0.1/24-192.168.0.2/24:ACCEPT:,"); + break; + case 2: + assertEquals(VRScripts.UPDATE_CONFIG, script); + assertEquals(VRScripts.NETWORK_ACL_CONFIG, args); + break; + default: + fail(); + } + } + + private ExecutionResult prepareNetworkElementCommand(final SetNetworkACLCommand cmd) { + final NicTO nic = cmd.getNic(); + nic.setDeviceId(3); + return new ExecutionResult(true, null); + } + + @Test + public void testSetupGuestNetworkCommand() { + final SetupGuestNetworkCommand cmd = generateSetupGuestNetworkCommand(); + final Answer answer = _resource.executeRequest(cmd); + assertTrue(answer.getResult()); + } + + private ExecutionResult prepareNetworkElementCommand(final SetupGuestNetworkCommand cmd) { + final NicTO nic = cmd.getNic(); + nic.setDeviceId(4); + return new ExecutionResult(true, null); + } + + protected SetupGuestNetworkCommand generateSetupGuestNetworkCommand() { + final NicTO nic = new NicTO(); + nic.setMac("01:23:45:67:89:AB"); + nic.setIp("10.1.1.1"); + nic.setNetmask("255.255.255.0"); + + final SetupGuestNetworkCommand cmd = new SetupGuestNetworkCommand("10.1.1.10-10.1.1.20", "cloud.test", false, "8.8.8.8", "8.8.4.4", true, nic); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_GUEST_IP, "10.1.1.2"); + cmd.setAccessDetail(NetworkElementCommand.GUEST_NETWORK_GATEWAY, "10.1.1.1"); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + + return cmd; + } + + private void verifyArgs(final SetupGuestNetworkCommand cmd, final String script, final String args) { + // TODO Check the contents of the json file + //assertEquals(script, VRScripts.VPC_GUEST_NETWORK); + //assertEquals(args, " -C -M 01:23:45:67:89:AB -d eth4 -i 10.1.1.2 -g 10.1.1.1 -m 24 -n 10.1.1.0 -s 8.8.8.8,8.8.4.4 -e cloud.test"); + } + + @Test + public void testSetMonitorServiceCommand() { + final SetMonitorServiceCommand cmd = generateSetMonitorServiceCommand(); + final Answer answer = _resource.executeRequest(cmd); + assertTrue(answer.getResult()); + } + + protected SetMonitorServiceCommand generateSetMonitorServiceCommand() { + final List services = new ArrayList<>(); + services.add(new MonitorServiceTO("service", "process", "name", "path", "file", true)); + services.add(new MonitorServiceTO("service_2", "process_2", "name_2", "path_2", "file_2", false)); + + final SetMonitorServiceCommand cmd = new SetMonitorServiceCommand(services); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + + return cmd; + } + + private void verifyArgs(final SetMonitorServiceCommand cmd, final String script, final String args) { + assertEquals(script, VRScripts.MONITOR_SERVICE); + assertEquals(args, " -c [service]:processname=process:servicename=name:pidfile=file:,[service_2]:processname=process_2:servicename=name_2:pidfile=file_2:,"); + } + + @Test + public void testSite2SiteVpnCfgCommand() { + _count = 0; + + Site2SiteVpnCfgCommand cmd = new Site2SiteVpnCfgCommand(true, "64.10.1.10", "64.10.1.1", "192.168.1.1/16", "124.10.1.10", "192.168.100.1/24", "3des-sha1,aes128-sha1;modp1536", "3des-sha1,aes128-md5", "psk", Long.valueOf(1800), Long.valueOf(1800), true, false, false); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + Answer answer = _resource.executeRequest(cmd); + assertTrue(answer.getResult()); + + cmd = new Site2SiteVpnCfgCommand(true, "64.10.1.10", "64.10.1.1", "192.168.1.1/16", "124.10.1.10", "192.168.100.1/24", "3des-sha1,aes128-sha1;modp1536", "3des-sha1,aes128-md5", "psk", Long.valueOf(1800), Long.valueOf(1800), false, true, false); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + answer = _resource.executeRequest(cmd); + assertTrue(answer.getResult()); + + cmd = new Site2SiteVpnCfgCommand(false, "64.10.1.10", "64.10.1.1", "192.168.1.1/16", "124.10.1.10", "192.168.100.1/24", "3des-sha1,aes128-sha1;modp1536", "3des-sha1,aes128-md5", "psk", Long.valueOf(1800), Long.valueOf(1800), false, true, false); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + answer = _resource.executeRequest(cmd); + assertTrue(answer.getResult()); + } + + private void verifyArgs(final Site2SiteVpnCfgCommand cmd, final String script, final String args) { + _count ++; + + assertEquals(script, VRScripts.S2SVPN_IPSEC); + switch (_count) { + case 1: + assertEquals(args, "-A -l 64.10.1.10 -n 192.168.1.1/16 -g 64.10.1.1 -r 124.10.1.10 -N 192.168.100.1/24 -e \"3des-sha1,aes128-md5\" -i \"3des-sha1,aes128-sha1;modp1536\" -t 1800 -T 1800 -s \"psk\" -d 1"); + break; + case 2: + assertEquals(args, "-A -l 64.10.1.10 -n 192.168.1.1/16 -g 64.10.1.1 -r 124.10.1.10 -N 192.168.100.1/24 -e \"3des-sha1,aes128-md5\" -i \"3des-sha1,aes128-sha1;modp1536\" -t 1800 -T 1800 -s \"psk\" -d 0 -p "); + break; + case 3: + assertEquals(args, "-D -r 124.10.1.10 -n 192.168.1.1/16 -N 192.168.100.1/24"); + break; + default: + fail(); + } + } + + @Test + public void testRemoteAccessVpnCfgCommand() { + _count = 0; + + Answer answer = _resource.executeRequest(generateRemoteAccessVpnCfgCommand1()); + assertTrue(answer.getResult()); + + answer = _resource.executeRequest(generateRemoteAccessVpnCfgCommand2()); + assertTrue(answer.getResult()); + + answer = _resource.executeRequest(generateRemoteAccessVpnCfgCommand3()); + assertTrue(answer.getResult()); + } + + protected RemoteAccessVpnCfgCommand generateRemoteAccessVpnCfgCommand1() { + final RemoteAccessVpnCfgCommand cmd = new RemoteAccessVpnCfgCommand(true, "124.10.10.10", "10.10.1.1", "10.10.1.10-10.10.1.20", "sharedkey", false); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + cmd.setLocalCidr("10.1.1.1/24"); + return cmd; + } + + protected RemoteAccessVpnCfgCommand generateRemoteAccessVpnCfgCommand2() { + final RemoteAccessVpnCfgCommand cmd = new RemoteAccessVpnCfgCommand(false, "124.10.10.10", "10.10.1.1", "10.10.1.10-10.10.1.20", "sharedkey", false); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + cmd.setLocalCidr("10.1.1.1/24"); + return cmd; + } + + protected RemoteAccessVpnCfgCommand generateRemoteAccessVpnCfgCommand3() { + final RemoteAccessVpnCfgCommand cmd = new RemoteAccessVpnCfgCommand(true, "124.10.10.10", "10.10.1.1", "10.10.1.10-10.10.1.20", "sharedkey", true); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + cmd.setLocalCidr("10.1.1.1/24"); + return cmd; + } + + private void verifyArgs(final RemoteAccessVpnCfgCommand cmd, final String script, final String args) { + _count ++; + + assertEquals(script, VRScripts.VPN_L2TP); + switch (_count) { + case 1: + assertEquals(args, "-r 10.10.1.10-10.10.1.20 -p sharedkey -s 124.10.10.10 -l 10.10.1.1 -c -C 10.1.1.1/24 -i eth2"); + break; + case 2: + assertEquals(args, "-d -s 124.10.10.10 -C 10.1.1.1/24 -i eth2"); + break; + case 3: + assertEquals(args, "-r 10.10.1.10-10.10.1.20 -p sharedkey -s 124.10.10.10 -l 10.10.1.1 -c -C 10.1.1.1/24 -i eth1"); + break; + default: + fail(); + + } + } + + @Test + public void testFirewallRulesCommand() { + _count = 0; + + final Answer answer = _resource.executeRequest(generateSetFirewallRulesCommand()); + assertTrue(answer.getResult()); + + //TODO Didn't test egress rule because not able to generate FirewallRuleVO object + } + + protected SetFirewallRulesCommand generateSetFirewallRulesCommand() { + final List rules = new ArrayList<>(); + final List sourceCidrs = new ArrayList<>(); + sourceCidrs.add("10.10.1.1/24"); + sourceCidrs.add("10.10.1.2/24"); + rules.add(new FirewallRuleTO(1, "64.10.10.10", "TCP", 22, 80, false, false, Purpose.Firewall, sourceCidrs, 0, 0)); + rules.add(new FirewallRuleTO(2, "64.10.10.10", "ICMP", 0, 0, false, false, Purpose.Firewall, sourceCidrs, -1, -1)); + rules.add(new FirewallRuleTO(3, "64.10.10.10", "ICMP", 0, 0, true, true, Purpose.Firewall, sourceCidrs, -1, -1)); + final SetFirewallRulesCommand cmd = new SetFirewallRulesCommand(rules); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + + return cmd; + } + + @Test + public void testVmDataCommand() { + final Answer answer = _resource.executeRequest(generateVmDataCommand()); + assertTrue(answer.getResult()); + } + + protected VmDataCommand generateVmDataCommand() { + final VmDataCommand cmd = new VmDataCommand("10.1.10.4", "i-4-VM", true); + // if you add new metadata files, also edit systemvm/patches/debian/config/var/www/html/latest/.htaccess + cmd.addVmData("userdata", "user-data", "user-data"); + cmd.addVmData("metadata", "service-offering", "serviceOffering"); + cmd.addVmData("metadata", "availability-zone", "zoneName"); + cmd.addVmData("metadata", "local-ipv4", "10.1.10.4"); + cmd.addVmData("metadata", "local-hostname", "test-vm"); + cmd.addVmData("metadata", "public-ipv4", "110.1.10.4"); + cmd.addVmData("metadata", "public-hostname", "hostname"); + cmd.addVmData("metadata", "instance-id", "i-4-VM"); + cmd.addVmData("metadata", "vm-id", "4"); + cmd.addVmData("metadata", "public-keys", "publickey"); + cmd.addVmData("metadata", "cloud-identifier", "CloudStack-{test}"); + + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + + return cmd; + } + + private void verifyArgs(final VmDataCommand cmd, final String script, final String args) { + assertEquals(script, VRScripts.UPDATE_CONFIG); + assertEquals(args, VRScripts.VM_METADATA_CONFIG); + } + + @Test + public void testSavePasswordCommand() { + final Answer answer = _resource.executeRequest(generateSavePasswordCommand()); + assertTrue(answer.getResult()); + } + + protected SavePasswordCommand generateSavePasswordCommand() { + final SavePasswordCommand cmd = new SavePasswordCommand("123pass", "10.1.10.4", "i-4-VM", true); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + return cmd; + } + + private void verifyArgs(final SavePasswordCommand cmd, final String script, final String args) { + assertEquals(script, VRScripts.PASSWORD); + assertEquals(args, "-v 10.1.10.4 -p 123pass"); + } + + @Test + public void testDhcpEntryCommand() { + _count = 0; + + Answer answer = _resource.executeRequest(generateDhcpEntryCommand1()); + assertTrue(answer.getResult()); + + answer = _resource.executeRequest(generateDhcpEntryCommand2()); + assertTrue(answer.getResult()); + + answer = _resource.executeRequest(generateDhcpEntryCommand3()); + assertTrue(answer.getResult()); + } + + protected DhcpEntryCommand generateDhcpEntryCommand1() { + final DhcpEntryCommand cmd = new DhcpEntryCommand("12:34:56:78:90:AB", "10.1.10.2", "vm1", null, true); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + return cmd; + } + + protected DhcpEntryCommand generateDhcpEntryCommand2() { + final DhcpEntryCommand cmd = new DhcpEntryCommand("12:34:56:78:90:AB", null, "vm1", "2001:db8:0:0:0:ff00:42:8329", true); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + cmd.setDuid(NetUtils.getDuidLL(cmd.getVmMac())); + return cmd; + } + + protected DhcpEntryCommand generateDhcpEntryCommand3() { + final DhcpEntryCommand cmd = new DhcpEntryCommand("12:34:56:78:90:AB", "10.1.10.2", "vm1", "2001:db8:0:0:0:ff00:42:8329", true); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + cmd.setDuid(NetUtils.getDuidLL(cmd.getVmMac())); + return cmd; + } + + private void verifyArgs(final DhcpEntryCommand cmd, final String script, final String args) { + _count ++; + assertEquals(script, VRScripts.DHCP); + switch (_count) { + case 1: + assertEquals(args, " -m 12:34:56:78:90:AB -4 10.1.10.2 -h vm1"); + break; + case 2: + assertEquals(args, " -m 12:34:56:78:90:AB -h vm1 -6 2001:db8:0:0:0:ff00:42:8329 -u 00:03:00:01:12:34:56:78:90:AB"); + break; + case 3: + assertEquals(args, " -m 12:34:56:78:90:AB -4 10.1.10.2 -h vm1 -6 2001:db8:0:0:0:ff00:42:8329 -u 00:03:00:01:12:34:56:78:90:AB"); + break; + default: + fail(); + } + } + + @Test + public void testCreateIpAliasCommand() { + final Answer answer = _resource.executeRequest(generateCreateIpAliasCommand()); + assertTrue(answer.getResult()); + } + + protected CreateIpAliasCommand generateCreateIpAliasCommand() { + final List aliases = new ArrayList<>(); + aliases.add(new IpAliasTO("169.254.3.10", "255.255.255.0", "1")); + aliases.add(new IpAliasTO("169.254.3.11", "255.255.255.0", "2")); + aliases.add(new IpAliasTO("169.254.3.12", "255.255.255.0", "3")); + final CreateIpAliasCommand cmd = new CreateIpAliasCommand("169.254.3.10", aliases); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + + return cmd; + } + + @Test + public void testDeleteIpAliasCommand() { + final Answer answer = _resource.executeRequest(generateDeleteIpAliasCommand()); + assertTrue(answer.getResult()); + } + + protected DeleteIpAliasCommand generateDeleteIpAliasCommand() { + final List aliases = new ArrayList<>(); + aliases.add(new IpAliasTO("169.254.3.10", "255.255.255.0", "1")); + aliases.add(new IpAliasTO("169.254.3.11", "255.255.255.0", "2")); + aliases.add(new IpAliasTO("169.254.3.12", "255.255.255.0", "3")); + final DeleteIpAliasCommand cmd = new DeleteIpAliasCommand("169.254.10.1", aliases, aliases); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + return cmd; + } + + @Test + public void testDnsMasqConfigCommand() { + final Answer answer = _resource.executeRequest(generateDnsMasqConfigCommand()); + assertTrue(answer.getResult()); + } + + protected DnsMasqConfigCommand generateDnsMasqConfigCommand() { + final List dhcps = new ArrayList<>(); + dhcps.add(new DhcpTO("10.1.20.2", "10.1.20.1", "255.255.255.0", "10.1.20.5")); + dhcps.add(new DhcpTO("10.1.21.2", "10.1.21.1", "255.255.255.0", "10.1.21.5")); + final DnsMasqConfigCommand cmd = new DnsMasqConfigCommand(dhcps); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + return cmd; + } + + private void verifyArgs(final DnsMasqConfigCommand cmd, final String script, final String args) { + assertEquals(script, VRScripts.DNSMASQ_CONFIG); + assertEquals(args, "10.1.20.2:10.1.20.1:255.255.255.0:10.1.20.5-10.1.21.2:10.1.21.1:255.255.255.0:10.1.21.5-"); + } + + @Test + public void testLoadBalancerConfigCommand() { + _count = 0; + _file = ""; + + Answer answer = _resource.executeRequest(generateLoadBalancerConfigCommand1()); + assertTrue(answer.getResult()); + + answer = _resource.executeRequest(generateLoadBalancerConfigCommand2()); + assertTrue(answer.getResult()); + } + + protected LoadBalancerConfigCommand generateLoadBalancerConfigCommand1() { + final List lbs = new ArrayList<>(); + final List dests = new ArrayList<>(); + dests.add(new LbDestination(80, 8080, "10.1.10.2", false)); + dests.add(new LbDestination(80, 8080, "10.1.10.2", true)); + lbs.add(new LoadBalancerTO(UUID.randomUUID().toString(), "64.10.1.10", 80, "tcp", "algo", false, false, false, dests)); + final LoadBalancerTO[] arrayLbs = new LoadBalancerTO[lbs.size()]; + lbs.toArray(arrayLbs); + final NicTO nic = new NicTO(); + final LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(arrayLbs, "64.10.2.10", "10.1.10.2", "192.168.1.2", nic, null, "1000", false); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_IP, "10.1.10.2"); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + return cmd; + } + + protected LoadBalancerConfigCommand generateLoadBalancerConfigCommand2() { + final List lbs = new ArrayList<>(); + final List dests = new ArrayList<>(); + dests.add(new LbDestination(80, 8080, "10.1.10.2", false)); + dests.add(new LbDestination(80, 8080, "10.1.10.2", true)); + lbs.add(new LoadBalancerTO(UUID.randomUUID().toString(), "64.10.1.10", 80, "tcp", "algo", false, false, false, dests)); + final LoadBalancerTO[] arrayLbs = new LoadBalancerTO[lbs.size()]; + lbs.toArray(arrayLbs); + final NicTO nic = new NicTO(); + nic.setIp("10.1.10.2"); + final LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(arrayLbs, "64.10.2.10", "10.1.10.2", "192.168.1.2", nic, Long.valueOf(1), "1000", false); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_IP, "10.1.10.2"); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); + return cmd; + } + + protected void verifyFile(final LoadBalancerConfigCommand cmd, final String path, final String filename, final String content) { + _count ++; + switch (_count) { + case 1: + case 3: + _file = path + filename; + assertEquals(path, "/etc/haproxy/"); + assertTrue(filename.startsWith("haproxy.cfg.new")); + assertEquals(content, "global\n" + + "\tlog 127.0.0.1:3914 local0 warning\n" + + "\tmaxconn 1000\n" + + "\tmaxpipes 250\n" + + "\tchroot /var/lib/haproxy\n" + + "\tuser haproxy\n" + + "\tgroup haproxy\n" + + "\tdaemon\n" + + "\t \n" + + "defaults\n" + + "\tlog global\n" + + "\tmode tcp\n" + + "\toption dontlognull\n" + + "\tretries 3\n" + + "\toption redispatch\n" + + "\toption forwardfor\n" + + "\toption forceclose\n" + + "\ttimeout connect 5000\n" + + "\ttimeout client 50000\n" + + "\ttimeout server 50000\n" + + "\n" + + "listen stats_on_guest 10.1.10.2:8081\n" + + "\tmode http\n" + + "\toption httpclose\n" + + "\tstats enable\n" + + "\tstats uri /admin?stats\n" + + "\tstats realm Haproxy\\ Statistics\n" + + "\tstats auth admin1:AdMiN123\n" + + "\n" + + "\t \n" + + "listen 64_10_1_10-80 64.10.1.10:80\n" + + "\tbalance algo\n" + + "\tserver 64_10_1_10-80_0 10.1.10.2:80 check\n" + + "\tmode http\n" + + "\toption httpclose\n" + + "\t \n" + + "\t \n"); + break; + default: + fail(); + } + } + + private void verifyArgs(final LoadBalancerConfigCommand cmd, final String script, final String args) { + _count ++; + switch (_count) { + case 2: + assertEquals(script, VRScripts.LB); + assertEquals(args, " -i 10.1.10.2 -f " + _file + " -a 64.10.1.10:80:, -s 10.1.10.2:8081:0/0:,,"); + break; + default: + fail(); + } + } + + @Test + @Ignore("Ignore this test while we are experimenting with the commands.") + public void testAggregationCommands() { + final List cmds = new LinkedList<>(); + final AggregationControlCommand startCmd = new AggregationControlCommand(Action.Start, ROUTERNAME, ROUTERIP, ROUTERGUESTIP); + cmds.add(startCmd); + cmds.add(generateIpAssocCommand()); + cmds.add(generateIpAssocVpcCommand()); + + cmds.add(generateSetFirewallRulesCommand()); + + cmds.add(generateSetPortForwardingRulesCommand()); + cmds.add(generateSetPortForwardingRulesVpcCommand()); + + cmds.add(generateCreateIpAliasCommand()); + cmds.add(generateDeleteIpAliasCommand()); + cmds.add(generateDnsMasqConfigCommand()); + + cmds.add(generateRemoteAccessVpnCfgCommand1()); + cmds.add(generateRemoteAccessVpnCfgCommand2()); + cmds.add(generateRemoteAccessVpnCfgCommand3()); + + //cmds.add(generateLoadBalancerConfigCommand1()); + //cmds.add(generateLoadBalancerConfigCommand2()); + + cmds.add(generateSetPortForwardingRulesCommand()); + cmds.add(generateSetPortForwardingRulesVpcCommand()); + + cmds.add(generateDhcpEntryCommand1()); + cmds.add(generateDhcpEntryCommand2()); + cmds.add(generateDhcpEntryCommand3()); + + cmds.add(generateSavePasswordCommand()); + cmds.add(generateVmDataCommand()); + + final AggregationControlCommand finishCmd = new AggregationControlCommand(Action.Finish, ROUTERNAME, ROUTERIP, ROUTERGUESTIP); + cmds.add(finishCmd); + + for (final NetworkElementCommand cmd : cmds) { + final Answer answer = _resource.executeRequest(cmd); + assertTrue(answer.getResult()); + } + } + + private void verifyArgs(final AggregationControlCommand cmd, final String script, final String args) { + assertEquals(script, VRScripts.VR_CFG); + assertTrue(args.startsWith("-c /var/cache/cloud/VR-")); + assertTrue(args.endsWith(".cfg")); + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/test/java/com/cloud/agent/transport/RequestTest.java b/cosmic-core/nucleo/src/test/java/com/cloud/agent/transport/RequestTest.java new file mode 100644 index 0000000000..dd71ce62e7 --- /dev/null +++ b/cosmic-core/nucleo/src/test/java/com/cloud/agent/transport/RequestTest.java @@ -0,0 +1,252 @@ +// +// 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.agent.transport; + +import java.nio.ByteBuffer; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.GetHostStatsCommand; +import com.cloud.agent.api.SecStorageFirewallCfgCommand; +import com.cloud.agent.api.UpdateHostPasswordCommand; +import com.cloud.agent.api.storage.DownloadAnswer; +import com.cloud.agent.api.storage.ListTemplateCommand; +import com.cloud.agent.api.to.NfsTO; +import com.cloud.exception.UnsupportedVersionException; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.serializer.GsonHelper; +import com.cloud.storage.DataStoreRole; +import com.cloud.storage.Storage.ImageFormat; +import com.cloud.storage.Storage.TemplateType; +import com.cloud.storage.VMTemplateStorageResourceAssoc.Status; +import com.cloud.template.VirtualMachineTemplate; + +import org.apache.cloudstack.storage.command.DownloadCommand; +import org.apache.cloudstack.storage.to.TemplateObjectTO; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.junit.Assert; +import org.mockito.Mockito; + +import junit.framework.TestCase; + +/** + * + * + * + * + */ + +public class RequestTest extends TestCase { + private static final Logger s_logger = Logger.getLogger(RequestTest.class); + + public void testSerDeser() { + s_logger.info("Testing serializing and deserializing works as expected"); + + s_logger.info("UpdateHostPasswordCommand should have two parameters that doesn't show in logging"); + UpdateHostPasswordCommand cmd1 = new UpdateHostPasswordCommand("abc", "def"); + s_logger.info("SecStorageFirewallCfgCommand has a context map that shouldn't show up in debug level"); + SecStorageFirewallCfgCommand cmd2 = new SecStorageFirewallCfgCommand(); + s_logger.info("GetHostStatsCommand should not show up at all in debug level"); + GetHostStatsCommand cmd3 = new GetHostStatsCommand("hostguid", "hostname", 101); + cmd2.addPortConfig("abc", "24", true, "eth0"); + cmd2.addPortConfig("127.0.0.1", "44", false, "eth1"); + Request sreq = new Request(2, 3, new Command[] {cmd1, cmd2, cmd3}, true, true); + sreq.setSequence(892403717); + + Logger logger = Logger.getLogger(GsonHelper.class); + Level level = logger.getLevel(); + + logger.setLevel(Level.DEBUG); + String log = sreq.log("Debug", true, Level.DEBUG); + assert (log.contains(UpdateHostPasswordCommand.class.getSimpleName())); + assert (log.contains(SecStorageFirewallCfgCommand.class.getSimpleName())); + assert (!log.contains(GetHostStatsCommand.class.getSimpleName())); + assert (!log.contains("username")); + assert (!log.contains("password")); + + logger.setLevel(Level.TRACE); + log = sreq.log("Trace", true, Level.TRACE); + assert (log.contains(UpdateHostPasswordCommand.class.getSimpleName())); + assert (log.contains(SecStorageFirewallCfgCommand.class.getSimpleName())); + assert (log.contains(GetHostStatsCommand.class.getSimpleName())); + assert (!log.contains("username")); + assert (!log.contains("password")); + + logger.setLevel(Level.INFO); + log = sreq.log("Info", true, Level.INFO); + assert (log == null); + + logger.setLevel(level); + + byte[] bytes = sreq.getBytes(); + + assert Request.getSequence(bytes) == 892403717; + assert Request.getManagementServerId(bytes) == 3; + assert Request.getAgentId(bytes) == 2; + assert Request.getViaAgentId(bytes) == 2; + Request creq = null; + try { + creq = Request.parse(bytes); + } catch (ClassNotFoundException e) { + s_logger.error("Unable to parse bytes: ", e); + } catch (UnsupportedVersionException e) { + s_logger.error("Unable to parse bytes: ", e); + } + + assert creq != null : "Couldn't get the request back"; + + compareRequest(creq, sreq); + + Answer ans = new Answer(cmd1, true, "No Problem"); + Response cresp = new Response(creq, ans); + + bytes = cresp.getBytes(); + + Response sresp = null; + try { + sresp = Response.parse(bytes); + } catch (ClassNotFoundException e) { + s_logger.error("Unable to parse bytes: ", e); + } catch (UnsupportedVersionException e) { + s_logger.error("Unable to parse bytes: ", e); + } + + assert sresp != null : "Couldn't get the response back"; + + compareRequest(cresp, sresp); + } + + public void testSerDeserTO() { + s_logger.info("Testing serializing and deserializing interface TO works as expected"); + + NfsTO nfs = new NfsTO("nfs://192.168.56.10/opt/storage/secondary", DataStoreRole.Image); + // SecStorageSetupCommand cmd = new SecStorageSetupCommand(nfs, "nfs://192.168.56.10/opt/storage/secondary", null); + ListTemplateCommand cmd = new ListTemplateCommand(nfs); + Request sreq = new Request(2, 3, cmd, true); + sreq.setSequence(892403718); + + byte[] bytes = sreq.getBytes(); + + assert Request.getSequence(bytes) == 892403718; + assert Request.getManagementServerId(bytes) == 3; + assert Request.getAgentId(bytes) == 2; + assert Request.getViaAgentId(bytes) == 2; + Request creq = null; + try { + creq = Request.parse(bytes); + } catch (ClassNotFoundException e) { + s_logger.error("Unable to parse bytes: ", e); + } catch (UnsupportedVersionException e) { + s_logger.error("Unable to parse bytes: ", e); + } + + assert creq != null : "Couldn't get the request back"; + + compareRequest(creq, sreq); + assertEquals("nfs://192.168.56.10/opt/storage/secondary", ((NfsTO)((ListTemplateCommand)creq.getCommand()).getDataStore()).getUrl()); + } + + public void testDownload() { + s_logger.info("Testing Download answer"); + VirtualMachineTemplate template = Mockito.mock(VirtualMachineTemplate.class); + Mockito.when(template.getId()).thenReturn(1L); + Mockito.when(template.getFormat()).thenReturn(ImageFormat.QCOW2); + Mockito.when(template.getName()).thenReturn("templatename"); + Mockito.when(template.getTemplateType()).thenReturn(TemplateType.USER); + Mockito.when(template.getDisplayText()).thenReturn("displayText"); + Mockito.when(template.getHypervisorType()).thenReturn(HypervisorType.KVM); + Mockito.when(template.getUrl()).thenReturn("url"); + + NfsTO nfs = new NfsTO("secUrl", DataStoreRole.Image); + TemplateObjectTO to = new TemplateObjectTO(template); + to.setImageDataStore(nfs); + DownloadCommand cmd = new DownloadCommand(to, 30000000l); + Request req = new Request(1, 1, cmd, true); + + req.logD("Debug for Download"); + + DownloadAnswer answer = new DownloadAnswer("jobId", 50, "errorString", Status.ABANDONED, "filesystempath", "installpath", 10000000, 20000000, "chksum"); + Response resp = new Response(req, answer); + resp.logD("Debug for Download"); + + } + + public void testCompress() { + s_logger.info("testCompress"); + int len = 800000; + ByteBuffer inputBuffer = ByteBuffer.allocate(len); + for (int i = 0; i < len; i++) { + inputBuffer.array()[i] = 1; + } + inputBuffer.limit(len); + ByteBuffer compressedBuffer = ByteBuffer.allocate(len); + compressedBuffer = Request.doCompress(inputBuffer, len); + s_logger.info("compressed length: " + compressedBuffer.limit()); + ByteBuffer decompressedBuffer = ByteBuffer.allocate(len); + decompressedBuffer = Request.doDecompress(compressedBuffer, len); + for (int i = 0; i < len; i++) { + if (inputBuffer.array()[i] != decompressedBuffer.array()[i]) { + Assert.fail("Fail at " + i); + } + } + } + + public void testLogging() { + s_logger.info("Testing Logging"); + GetHostStatsCommand cmd3 = new GetHostStatsCommand("hostguid", "hostname", 101); + Request sreq = new Request(2, 3, new Command[] {cmd3}, true, true); + sreq.setSequence(1); + Logger logger = Logger.getLogger(GsonHelper.class); + Level level = logger.getLevel(); + + logger.setLevel(Level.DEBUG); + String log = sreq.log("Debug", true, Level.DEBUG); + assert (log == null); + + log = sreq.log("Debug", false, Level.DEBUG); + assert (log != null); + + logger.setLevel(Level.TRACE); + log = sreq.log("Trace", true, Level.TRACE); + assert (log.contains(GetHostStatsCommand.class.getSimpleName())); + s_logger.debug(log); + + logger.setLevel(level); + } + + protected void compareRequest(Request req1, Request req2) { + assert req1.getSequence() == req2.getSequence(); + assert req1.getAgentId() == req2.getAgentId(); + assert req1.getManagementServerId() == req2.getManagementServerId(); + assert req1.isControl() == req2.isControl(); + assert req1.isFromServer() == req2.isFromServer(); + assert req1.executeInSequence() == req2.executeInSequence(); + assert req1.stopOnError() == req2.stopOnError(); + assert req1.getVersion().equals(req2.getVersion()); + assert req1.getViaAgentId() == req2.getViaAgentId(); + Command[] cmd1 = req1.getCommands(); + Command[] cmd2 = req2.getCommands(); + for (int i = 0; i < cmd1.length; i++) { + assert cmd1[i].getClass().equals(cmd2[i].getClass()); + } + } + +} diff --git a/cosmic-core/nucleo/src/test/java/com/cloud/network/HAProxyConfiguratorTest.java b/cosmic-core/nucleo/src/test/java/com/cloud/network/HAProxyConfiguratorTest.java new file mode 100644 index 0000000000..db23ca17e1 --- /dev/null +++ b/cosmic-core/nucleo/src/test/java/com/cloud/network/HAProxyConfiguratorTest.java @@ -0,0 +1,121 @@ +// +// 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.network; + +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import com.cloud.agent.api.routing.LoadBalancerConfigCommand; +import com.cloud.agent.api.to.LoadBalancerTO; +import com.cloud.network.lb.LoadBalancingRule.LbDestination; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * @author dhoogland + * + */ +public class HAProxyConfiguratorTest { + + /** + * @throws java.lang.Exception + */ + @BeforeClass + public static void setUpBeforeClass() throws Exception { + } + + /** + * @throws java.lang.Exception + */ + @AfterClass + public static void tearDownAfterClass() throws Exception { + } + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + } + + /** + * @throws java.lang.Exception + */ + @After + public void tearDown() throws Exception { + } + + /** + * Test method for {@link com.cloud.network.HAProxyConfigurator#generateConfiguration(com.cloud.agent.api.routing.LoadBalancerConfigCommand)}. + */ + @Test + public void testGenerateConfigurationLoadBalancerConfigCommand() { + LoadBalancerTO lb = new LoadBalancerTO("1", "10.2.0.1", 80, "http", "bla", false, false, false, null); + LoadBalancerTO[] lba = new LoadBalancerTO[1]; + lba[0] = lb; + HAProxyConfigurator hpg = new HAProxyConfigurator(); + LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(lba, "10.0.0.1", "10.1.0.1", "10.1.1.1", null, 1L, "12", false); + String result = genConfig(hpg, cmd); + assertTrue("keepalive disabled should result in 'mode http' in the resulting haproxy config", result.contains("mode http")); + + cmd = new LoadBalancerConfigCommand(lba, "10.0.0.1", "10.1.0.1", "10.1.1.1", null, 1L, "4", true); + result = genConfig(hpg, cmd); + assertTrue("keepalive enabled should not result in 'mode http' in the resulting haproxy config", !result.contains("mode http")); + // TODO + // create lb command + // setup tests for + // maxconn (test for maxpipes as well) + // httpmode + } + + /** + * Test method for {@link com.cloud.network.HAProxyConfigurator#generateConfiguration(com.cloud.agent.api.routing.LoadBalancerConfigCommand)}. + */ + @Test + public void testGenerateConfigurationLoadBalancerProxyProtocolConfigCommand() { + final List dests = new ArrayList<>(); + dests.add(new LbDestination(443, 8443, "10.1.10.2", false)); + dests.add(new LbDestination(443, 8443, "10.1.10.2", true)); + LoadBalancerTO lb = new LoadBalancerTO("1", "10.2.0.1", 443, "tcp", "http", false, false, false, dests); + lb.setLbProtocol("tcp-proxy"); + LoadBalancerTO[] lba = new LoadBalancerTO[1]; + lba[0] = lb; + HAProxyConfigurator hpg = new HAProxyConfigurator(); + LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(lba, "10.0.0.1", "10.1.0.1", "10.1.1.1", null, 1L, "12", false); + String result = genConfig(hpg, cmd); + assertTrue("'send-proxy' should result if protocol is 'tcp-proxy'", result.contains("send-proxy")); + } + + private String genConfig(HAProxyConfigurator hpg, LoadBalancerConfigCommand cmd) { + String[] sa = hpg.generateConfiguration(cmd); + StringBuilder sb = new StringBuilder(); + for (String s : sa) { + sb.append(s).append('\n'); + } + return sb.toString(); + } + +} diff --git a/cosmic-core/nucleo/src/test/java/com/cloud/storage/template/LocalTemplateDownloaderTest.java b/cosmic-core/nucleo/src/test/java/com/cloud/storage/template/LocalTemplateDownloaderTest.java new file mode 100644 index 0000000000..ce8b2e1b04 --- /dev/null +++ b/cosmic-core/nucleo/src/test/java/com/cloud/storage/template/LocalTemplateDownloaderTest.java @@ -0,0 +1,43 @@ +// +// 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.storage.template; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThan; + +import java.io.File; + +import org.junit.Test; + +public class LocalTemplateDownloaderTest { + + @Test + public void localTemplateDownloaderTest() throws Exception { + final String url = new File("pom.xml").toURI().toURL().toString(); + final long defaultMaxTemplateSizeInBytes = TemplateDownloader.DEFAULT_MAX_TEMPLATE_SIZE_IN_BYTES; + final String tempDir = System.getProperty("java.io.tmpdir"); + final TemplateDownloader td = new LocalTemplateDownloader(url, tempDir, defaultMaxTemplateSizeInBytes); + + final long bytes = td.download(true, null); + + assertThat(bytes, greaterThan(0L)); + } + +} diff --git a/cosmic-core/nucleo/src/test/java/com/cloud/storage/template/QCOW2ProcessorTest.java b/cosmic-core/nucleo/src/test/java/com/cloud/storage/template/QCOW2ProcessorTest.java new file mode 100644 index 0000000000..d72ce836a4 --- /dev/null +++ b/cosmic-core/nucleo/src/test/java/com/cloud/storage/template/QCOW2ProcessorTest.java @@ -0,0 +1,110 @@ +/* + * 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.storage.template; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import com.cloud.exception.InternalErrorException; +import com.cloud.storage.Storage; +import com.cloud.storage.StorageLayer; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class QCOW2ProcessorTest { + QCOW2Processor processor; + + @Mock + StorageLayer mockStorageLayer; + + @Before + public void setUp() throws Exception { + processor = Mockito.spy(new QCOW2Processor()); + Map params = new HashMap(); + params.put(StorageLayer.InstanceConfigKey, mockStorageLayer); + processor.configure("VHD Processor", params); + } + + @Test(expected = InternalErrorException.class) + public void testProcessWhenVirtualSizeThrowsException() throws Exception { + String templatePath = "/tmp"; + String templateName = "template"; + + Mockito.when(mockStorageLayer.exists(Mockito.anyString())).thenReturn(true); + File mockFile = Mockito.mock(File.class); + + Mockito.when(mockStorageLayer.getFile(Mockito.anyString())).thenReturn(mockFile); + Mockito.when(mockStorageLayer.getSize(Mockito.anyString())).thenReturn(1000L); + Mockito.doThrow(new IOException("virtual size calculation failed")).when(processor).getTemplateVirtualSize((File)Mockito.any()); + + processor.process(templatePath, null, templateName); + } + + @Test + public void testProcess() throws Exception { + String templatePath = "/tmp"; + String templateName = "template"; + long virtualSize = 2000; + long actualSize = 1000; + + Mockito.when(mockStorageLayer.exists(Mockito.anyString())).thenReturn(true); + File mockFile = Mockito.mock(File.class); + + Mockito.when(mockStorageLayer.getFile(Mockito.anyString())).thenReturn(mockFile); + Mockito.when(mockStorageLayer.getSize(Mockito.anyString())).thenReturn(actualSize); + Mockito.doReturn(virtualSize).when(processor).getTemplateVirtualSize((File)Mockito.any()); + + Processor.FormatInfo info = processor.process(templatePath, null, templateName); + Assert.assertEquals(Storage.ImageFormat.QCOW2, info.format); + Assert.assertEquals(actualSize, info.size); + Assert.assertEquals(virtualSize, info.virtualSize); + Assert.assertEquals(templateName + ".qcow2", info.filename); + } + + @Test + public void testGetVirtualSizeWhenVirtualSizeThrowsException() throws Exception { + long virtualSize = 2000; + long actualSize = 1000; + File mockFile = Mockito.mock(File.class); + Mockito.when(mockFile.length()).thenReturn(actualSize); + Mockito.doThrow(new IOException("virtual size calculation failed")).when(processor).getTemplateVirtualSize((File)Mockito.any()); + Assert.assertEquals(actualSize, processor.getVirtualSize(mockFile)); + Mockito.verify(mockFile, Mockito.times(1)).length(); + } + + @Test + public void testGetVirtualSize() throws Exception { + long virtualSize = 2000; + long actualSize = 1000; + File mockFile = Mockito.mock(File.class); + Mockito.when(mockFile.length()).thenReturn(actualSize); + Mockito.doReturn(virtualSize).when(processor).getTemplateVirtualSize((File)Mockito.any()); + Assert.assertEquals(virtualSize, processor.getVirtualSize(mockFile)); + Mockito.verify(mockFile, Mockito.times(0)).length(); + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/test/java/com/cloud/storage/template/VhdProcessorTest.java b/cosmic-core/nucleo/src/test/java/com/cloud/storage/template/VhdProcessorTest.java new file mode 100644 index 0000000000..02a436e202 --- /dev/null +++ b/cosmic-core/nucleo/src/test/java/com/cloud/storage/template/VhdProcessorTest.java @@ -0,0 +1,111 @@ +/* + * 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.storage.template; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import com.cloud.exception.InternalErrorException; +import com.cloud.storage.Storage; +import com.cloud.storage.StorageLayer; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class VhdProcessorTest { + VhdProcessor processor; + + @Mock + StorageLayer mockStorageLayer; + + @Before + public void setUp() throws Exception { + processor = Mockito.spy(new VhdProcessor()); + Map params = new HashMap(); + params.put(StorageLayer.InstanceConfigKey, mockStorageLayer); + processor.configure("VHD Processor", params); + } + + @Test(expected = InternalErrorException.class) + public void testProcessWhenVirtualSizeThrowsException() throws Exception { + String templatePath = "/tmp"; + String templateName = "template"; + + Mockito.when(mockStorageLayer.exists(Mockito.anyString())).thenReturn(true); + File mockFile = Mockito.mock(File.class); + + Mockito.when(mockStorageLayer.getFile(Mockito.anyString())).thenReturn(mockFile); + Mockito.when(mockStorageLayer.getSize(Mockito.anyString())).thenReturn(1000L); + Mockito.doThrow(new IOException("virtual size calculation failed")).when(processor).getTemplateVirtualSize((File) Mockito.any()); + + processor.process(templatePath, null, templateName); + } + + @Test + public void testProcess() throws Exception { + String templatePath = "/tmp"; + String templateName = "template"; + long virtualSize = 2000; + long actualSize = 1000; + + Mockito.when(mockStorageLayer.exists(Mockito.anyString())).thenReturn(true); + File mockFile = Mockito.mock(File.class); + + Mockito.when(mockStorageLayer.getFile(Mockito.anyString())).thenReturn(mockFile); + Mockito.when(mockStorageLayer.getSize(Mockito.anyString())).thenReturn(actualSize); + Mockito.doReturn(virtualSize).when(processor).getTemplateVirtualSize((File) Mockito.any()); + + Processor.FormatInfo info = processor.process(templatePath, null, templateName); + Assert.assertEquals(Storage.ImageFormat.VHD, info.format); + Assert.assertEquals(actualSize, info.size); + Assert.assertEquals(virtualSize, info.virtualSize); + Assert.assertEquals(templateName + ".vhd", info.filename); + } + + @Test + public void testGetVirtualSizeWhenVirtualSizeThrowsException() throws Exception { + long virtualSize = 2000; + long actualSize = 1000; + File mockFile = Mockito.mock(File.class); + Mockito.when(mockFile.length()).thenReturn(actualSize); + Mockito.doThrow(new IOException("virtual size calculation failed")).when(processor).getTemplateVirtualSize((File) Mockito.any()); + Assert.assertEquals(actualSize, processor.getVirtualSize(mockFile)); + Mockito.verify(mockFile,Mockito.times(1)).length(); + } + + @Test + public void testGetVirtualSize() throws Exception{ + long virtualSize = 2000; + long actualSize = 1000; + File mockFile = Mockito.mock(File.class); + Mockito.when(mockFile.length()).thenReturn(actualSize); + Mockito.doReturn(virtualSize).when(processor).getTemplateVirtualSize((File) Mockito.any()); + Assert.assertEquals(virtualSize, processor.getVirtualSize(mockFile)); + Mockito.verify(mockFile,Mockito.times(0)).length(); + } +} \ No newline at end of file diff --git a/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/AgentControlAnswerTest.java b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/AgentControlAnswerTest.java new file mode 100644 index 0000000000..b76640cc12 --- /dev/null +++ b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/AgentControlAnswerTest.java @@ -0,0 +1,38 @@ +// +// 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 org.apache.cloudstack.api.agent.test; + +import static org.junit.Assert.assertFalse; + +import com.cloud.agent.api.AgentControlAnswer; +import com.cloud.agent.api.AgentControlCommand; + +import org.junit.Test; + +public class AgentControlAnswerTest { + AgentControlCommand acc = new AgentControlCommand(); + AgentControlAnswer aca = new AgentControlAnswer(acc); + + @Test + public void testExecuteInSequence() { + boolean b = acc.executeInSequence(); + assertFalse(b); + } +} diff --git a/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/AgentControlCommandTest.java b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/AgentControlCommandTest.java new file mode 100644 index 0000000000..8a0409a576 --- /dev/null +++ b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/AgentControlCommandTest.java @@ -0,0 +1,36 @@ +// +// 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 org.apache.cloudstack.api.agent.test; + +import static org.junit.Assert.assertFalse; + +import com.cloud.agent.api.AgentControlCommand; + +import org.junit.Test; + +public class AgentControlCommandTest { + AgentControlCommand acc = new AgentControlCommand(); + + @Test + public void testExecuteInSequence() { + boolean b = acc.executeInSequence(); + assertFalse(b); + } +} diff --git a/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/AnswerTest.java b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/AnswerTest.java new file mode 100644 index 0000000000..c9c96077f3 --- /dev/null +++ b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/AnswerTest.java @@ -0,0 +1,75 @@ +// +// 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 org.apache.cloudstack.api.agent.test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.cloud.agent.api.AgentControlCommand; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.UnsupportedAnswer; + +import org.junit.Test; + +public class AnswerTest { + AgentControlCommand acc = new AgentControlCommand(); + Answer a = new Answer(acc, true, "details"); + + @Test + public void testExecuteInSequence() { + final boolean b = a.executeInSequence(); + assertFalse(b); + } + + @Test + public void testGetResult() { + final boolean b = a.getResult(); + assertTrue(b); + } + + @Test + public void testGetDetails() { + final String d = a.getDetails(); + assertTrue(d.equals("details")); + } + + @Test + public void testCreateUnsupportedCommandAnswer() { + UnsupportedAnswer usa = Answer.createUnsupportedCommandAnswer(acc); + boolean b = usa.executeInSequence(); + assertFalse(b); + + b = usa.getResult(); + assertFalse(b); + + String d = usa.getDetails(); + assertTrue(d.contains("Unsupported command issued: " + acc.toString() + ". Are you sure you got the right type of server?")); + + usa = Answer.createUnsupportedVersionAnswer(acc); + b = usa.executeInSequence(); + assertFalse(b); + + b = usa.getResult(); + assertFalse(b); + + d = usa.getDetails(); + assertTrue(d.equals("Unsuppored Version.")); + } +} diff --git a/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/AttachIsoCommandTest.java b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/AttachIsoCommandTest.java new file mode 100644 index 0000000000..8c01896fcb --- /dev/null +++ b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/AttachIsoCommandTest.java @@ -0,0 +1,83 @@ +// +// 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 org.apache.cloudstack.api.agent.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.cloud.agent.api.AttachIsoCommand; + +import org.junit.Test; + +public class AttachIsoCommandTest { + AttachIsoCommand aic = new AttachIsoCommand("vmname", "isopath", false); + + @Test + public void testGetVmName() { + String vmName = aic.getVmName(); + assertTrue(vmName.equals("vmname")); + } + + @Test + public void testGetIsoPath() { + String isoPath = aic.getIsoPath(); + assertTrue(isoPath.equals("isopath")); + } + + @Test + public void testIsAttach() { + boolean b = aic.isAttach(); + assertFalse(b); + } + + @Test + public void testGetStoreUrl() { + aic.setStoreUrl("http://incubator.apache.org/cloudstack/"); + String url = aic.getStoreUrl(); + assertTrue(url.equals("http://incubator.apache.org/cloudstack/")); + } + + @Test + public void testExecuteInSequence() { + boolean b = aic.executeInSequence(); + assertTrue(b); + } + + @Test + public void testAllowCaching() { + boolean b = aic.allowCaching(); + assertTrue(b); + } + + @Test + public void testGetWait() { + int b; + aic.setWait(5); + b = aic.getWait(); + assertEquals(b, 5); + aic.setWait(-3); + b = aic.getWait(); + assertEquals(b, -3); + aic.setWait(0); + b = aic.getWait(); + assertEquals(b, 0); + } +} diff --git a/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/BackupSnapshotAnswerTest.java b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/BackupSnapshotAnswerTest.java new file mode 100644 index 0000000000..7b333a8f79 --- /dev/null +++ b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/BackupSnapshotAnswerTest.java @@ -0,0 +1,77 @@ +// +// 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 org.apache.cloudstack.api.agent.test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.cloud.agent.api.BackupSnapshotAnswer; +import com.cloud.agent.api.BackupSnapshotCommand; +import com.cloud.storage.StoragePool; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +public class BackupSnapshotAnswerTest { + private BackupSnapshotCommand bsc; + private BackupSnapshotAnswer bsa; + + @Before + public void setUp() { + + StoragePool pool = Mockito.mock(StoragePool.class); + + bsc = + new BackupSnapshotCommand("secondaryStoragePoolURL", 101L, 102L, 103L, 104L, 105L, "volumePath", pool, "snapshotUuid", "snapshotName", "prevSnapshotUuid", + "prevBackupUuid", false, "vmName", 5); + bsa = new BackupSnapshotAnswer(bsc, true, "results", "bussname", false); + } + + @Test + public void testExecuteInSequence() { + boolean b = bsa.executeInSequence(); + assertFalse(b); + } + + @Test + public void testIsFull() { + boolean b = bsa.isFull(); + assertFalse(b); + } + + @Test + public void testGetBackupSnapshotName() { + String name = bsa.getBackupSnapshotName(); + assertTrue(name.equals("bussname")); + } + + @Test + public void testGetResult() { + boolean b = bsa.getResult(); + assertTrue(b); + } + + @Test + public void testDetails() { + String details = bsa.getDetails(); + assertTrue(details.equals("results")); + } +} diff --git a/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/BackupSnapshotCommandTest.java b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/BackupSnapshotCommandTest.java new file mode 100644 index 0000000000..83cfc49cc8 --- /dev/null +++ b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/BackupSnapshotCommandTest.java @@ -0,0 +1,268 @@ +// +// 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 org.apache.cloudstack.api.agent.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import com.cloud.agent.api.BackupSnapshotCommand; +import com.cloud.agent.api.to.SwiftTO; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.storage.Storage.StoragePoolType; +import com.cloud.storage.StoragePool; +import com.cloud.storage.StoragePoolStatus; + +import org.junit.Test; + +public class BackupSnapshotCommandTest { + public StoragePool pool = new StoragePool() { + @Override + public long getId() { + return 1L; + }; + + @Override + public String getName() { + return "name"; + }; + + @Override + public String getUuid() { + return "bed9f83e-cac3-11e1-ac8a-0050568b007e"; + }; + + @Override + public StoragePoolType getPoolType() { + return StoragePoolType.Filesystem; + }; + + @Override + public Date getCreated() { + Date date = null; + try { + date = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss").parse("01/01/1970 12:12:12"); + } catch (ParseException e) { + e.printStackTrace(); + } + return date; + } + + @Override + public Date getUpdateTime() { + return new Date(); + }; + + @Override + public long getDataCenterId() { + return 0L; + }; + + @Override + public long getCapacityBytes() { + return 0L; + }; + + @Override + public long getUsedBytes() { + return 0L; + }; + + @Override + public Long getCapacityIops() { + return 0L; + } + + @Override + public Long getClusterId() { + return 0L; + }; + + @Override + public String getHostAddress() { + return "hostAddress"; + }; + + @Override + public String getPath() { + return "path"; + }; + + @Override + public String getUserInfo() { + return "userInfo"; + }; + + @Override + public boolean isShared() { + return false; + }; + + @Override + public boolean isLocal() { + return false; + }; + + @Override + public StoragePoolStatus getStatus() { + return StoragePoolStatus.Up; + }; + + @Override + public int getPort() { + return 25; + }; + + @Override + public Long getPodId() { + return 0L; + } + + @Override + public String getStorageProviderName() { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isInMaintenance() { + // TODO Auto-generated method stub + return false; + } + + @Override + public Hypervisor.HypervisorType getHypervisor() { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + ; + }; + + BackupSnapshotCommand bsc = new BackupSnapshotCommand("http://secondary.Storage.Url", 101L, 102L, 103L, 104L, 105L, "vPath", pool, + "420fa39c-4ef1-a83c-fd93-46dc1ff515ae", "sName", "9012793e-0657-11e2-bebc-0050568b0057", "7167e0b2-f5b0-11e1-8414-0050568b0057", false, "vmName", 5); + + BackupSnapshotCommand bsc1 = new BackupSnapshotCommand("http://secondary.Storage.Url", 101L, 102L, 103L, 104L, 105L, "vPath", pool, + "420fa39c-4ef1-a83c-fd93-46dc1ff515ae", "sName", "9012793e-0657-11e2-bebc-0050568b0057", "7167e0b2-f5b0-11e1-8414-0050568b0057", false, "vmName", 5); + + @Test + public void testGetSecondaryStorageUrl() { + String url = bsc.getSecondaryStorageUrl(); + assertTrue(url.equals("http://secondary.Storage.Url")); + } + + @Test + public void testGetDataCenterId() { + Long dcId = bsc.getDataCenterId(); + Long expected = 101L; + assertEquals(expected, dcId); + } + + @Test + public void testGetAccountId() { + Long aId = bsc.getAccountId(); + Long expected = 102L; + assertEquals(expected, aId); + } + + @Test + public void testGetVolumeId() { + Long vId = bsc.getVolumeId(); + Long expected = 103L; + assertEquals(expected, vId); + } + + @Test + public void testGetSnapshotId() { + Long ssId = bsc.getSnapshotId(); + Long expected = 104L; + assertEquals(expected, ssId); + } + + @Test + public void testGetCreated() { + try { + Date date = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss").parse("01/01/1970 12:12:12"); + Date d = pool.getCreated(); + assertTrue(d.compareTo(date) == 0); + } catch (ParseException e) { + e.printStackTrace(); + } + } + + @Test + public void testGetSwift() { + SwiftTO s1 = new SwiftTO(); + bsc.setSwift(s1); + SwiftTO s2 = bsc.getSwift(); + assertEquals(s1, s2); + } + + @Test + public void testGetSnapshotName() { + String ssName = bsc.getSnapshotName(); + assertTrue(ssName.equals("sName")); + } + + @Test + public void testGetSnapshotUuid() { + String uuid = bsc.getSnapshotUuid(); + assertTrue(uuid.equals("420fa39c-4ef1-a83c-fd93-46dc1ff515ae")); + } + + @Test + public void testGetPrevSnapshotUuid() { + String uuid = bsc.getPrevSnapshotUuid(); + assertTrue(uuid.equals("9012793e-0657-11e2-bebc-0050568b0057")); + } + + @Test + public void testGetPrevBackupUuid() { + String uuid = bsc.getPrevBackupUuid(); + assertTrue(uuid.equals("7167e0b2-f5b0-11e1-8414-0050568b0057")); + } + + @Test + public void testGetVolumePath() { + String path = bsc.getVolumePath(); + assertTrue(path.equals("vPath")); + + bsc.setVolumePath("vPath1"); + path = bsc.getVolumePath(); + assertTrue(path.equals("vPath1")); + + bsc1.setVolumePath("vPath2"); + path = bsc1.getVolumePath(); + assertTrue(path.equals("vPath2")); + } + + @Test + public void testExecuteInSequence() { + boolean b = bsc.executeInSequence(); + assertFalse(b); + + b = bsc1.executeInSequence(); + assertFalse(b); + } + +} diff --git a/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/BumpUpPriorityCommandTest.java b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/BumpUpPriorityCommandTest.java new file mode 100644 index 0000000000..580a44e2ba --- /dev/null +++ b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/BumpUpPriorityCommandTest.java @@ -0,0 +1,80 @@ +// +// 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 org.apache.cloudstack.api.agent.test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.cloud.agent.api.BumpUpPriorityCommand; +import com.cloud.agent.api.routing.NetworkElementCommand; + +import org.junit.Test; + +public class BumpUpPriorityCommandTest { + + BumpUpPriorityCommand bupc = new BumpUpPriorityCommand(); + + // test super class + @Test + public void testSuperGetAccessDetail() { + String value; + bupc.setAccessDetail(NetworkElementCommand.ACCOUNT_ID, "accountID"); + value = bupc.getAccessDetail(NetworkElementCommand.ACCOUNT_ID); + assertTrue(value.equals("accountID")); + + bupc.setAccessDetail(NetworkElementCommand.GUEST_NETWORK_CIDR, "GuestNetworkCIDR"); + value = bupc.getAccessDetail(NetworkElementCommand.GUEST_NETWORK_CIDR); + assertTrue(value.equals("GuestNetworkCIDR")); + + bupc.setAccessDetail(NetworkElementCommand.GUEST_NETWORK_GATEWAY, "GuestNetworkGateway"); + value = bupc.getAccessDetail(NetworkElementCommand.GUEST_NETWORK_GATEWAY); + assertTrue(value.equals("GuestNetworkGateway")); + + bupc.setAccessDetail(NetworkElementCommand.GUEST_VLAN_TAG, "GuestVlanTag"); + value = bupc.getAccessDetail(NetworkElementCommand.GUEST_VLAN_TAG); + assertTrue(value.equals("GuestVlanTag")); + + bupc.setAccessDetail(NetworkElementCommand.ROUTER_NAME, "RouterName"); + value = bupc.getAccessDetail(NetworkElementCommand.ROUTER_NAME); + assertTrue(value.equals("RouterName")); + + bupc.setAccessDetail(NetworkElementCommand.ROUTER_IP, "RouterIP"); + value = bupc.getAccessDetail(NetworkElementCommand.ROUTER_IP); + assertTrue(value.equals("RouterIP")); + + bupc.setAccessDetail(NetworkElementCommand.ROUTER_GUEST_IP, "RouterGuestIP"); + value = bupc.getAccessDetail(NetworkElementCommand.ROUTER_GUEST_IP); + assertTrue(value.equals("RouterGuestIP")); + + bupc.setAccessDetail(NetworkElementCommand.ZONE_NETWORK_TYPE, "ZoneNetworkType"); + value = bupc.getAccessDetail(NetworkElementCommand.ZONE_NETWORK_TYPE); + assertTrue(value.equals("ZoneNetworkType")); + + bupc.setAccessDetail(NetworkElementCommand.GUEST_BRIDGE, "GuestBridge"); + value = bupc.getAccessDetail(NetworkElementCommand.GUEST_BRIDGE); + assertTrue(value.equals("GuestBridge")); + } + + @Test + public void testExecuteInSequence() { + boolean b = bupc.executeInSequence(); + assertFalse(b); + } +} diff --git a/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/CancelCommandTest.java b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/CancelCommandTest.java new file mode 100644 index 0000000000..0184db0584 --- /dev/null +++ b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/CancelCommandTest.java @@ -0,0 +1,49 @@ +// +// 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 org.apache.cloudstack.api.agent.test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.cloud.agent.api.CancelCommand; + +import org.junit.Test; + +public class CancelCommandTest { + CancelCommand cc = new CancelCommand(123456789L, "goodreason"); + + @Test + public void testGetSequence() { + Long s = cc.getSequence(); + assertTrue(123456789L == s); + } + + @Test + public void testGetReason() { + String r = cc.getReason(); + assertTrue(r.equals("goodreason")); + } + + @Test + public void testExecuteInSequence() { + boolean b = cc.executeInSequence(); + assertFalse(b); + } +} diff --git a/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/ChangeAgentAnswerTest.java b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/ChangeAgentAnswerTest.java new file mode 100644 index 0000000000..87a1585866 --- /dev/null +++ b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/ChangeAgentAnswerTest.java @@ -0,0 +1,46 @@ +// +// 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 org.apache.cloudstack.api.agent.test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.cloud.agent.api.ChangeAgentAnswer; +import com.cloud.agent.api.ChangeAgentCommand; +import com.cloud.host.Status.Event; + +import org.junit.Test; + +public class ChangeAgentAnswerTest { + ChangeAgentCommand cac = new ChangeAgentCommand(123456789L, Event.AgentConnected); + ChangeAgentAnswer caa = new ChangeAgentAnswer(cac, true); + + @Test + public void testGetResult() { + boolean b = caa.getResult(); + assertTrue(b); + } + + @Test + public void testExecuteInSequence() { + boolean b = caa.executeInSequence(); + assertFalse(b); + } +} diff --git a/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/ChangeAgentCommandTest.java b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/ChangeAgentCommandTest.java new file mode 100644 index 0000000000..56e502560a --- /dev/null +++ b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/ChangeAgentCommandTest.java @@ -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. +// + +package org.apache.cloudstack.api.agent.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.cloud.agent.api.ChangeAgentCommand; +import com.cloud.host.Status.Event; + +import org.junit.Test; + +public class ChangeAgentCommandTest { + + ChangeAgentCommand cac = new ChangeAgentCommand(123456789L, Event.AgentConnected); + + @Test + public void testGetAgentId() { + Long aid = cac.getAgentId(); + assertTrue(123456789L == aid); + } + + @Test + public void testGetEvent() { + Event e = cac.getEvent(); + assertEquals(Event.AgentConnected, e); + } + + @Test + public void testExecuteInSequence() { + boolean b = cac.executeInSequence(); + assertFalse(b); + } +} diff --git a/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/CheckHealthAnswerTest.java b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/CheckHealthAnswerTest.java new file mode 100644 index 0000000000..c655a92352 --- /dev/null +++ b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/CheckHealthAnswerTest.java @@ -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. +// + +package org.apache.cloudstack.api.agent.test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.cloud.agent.api.CheckHealthAnswer; +import com.cloud.agent.api.CheckHealthCommand; + +import org.junit.Test; + +public class CheckHealthAnswerTest { + CheckHealthCommand chc = new CheckHealthCommand(); + CheckHealthAnswer cha = new CheckHealthAnswer(chc, true); + + @Test + public void testGetResult() { + boolean r = cha.getResult(); + assertTrue(r); + } + + @Test + public void testGetDetails() { + String d = cha.getDetails(); + boolean r = cha.getResult(); + assertTrue(d.equals("resource is " + (r ? "alive" : "not alive"))); + } + + @Test + public void testExecuteInSequence() { + boolean b = cha.executeInSequence(); + assertFalse(b); + } +} diff --git a/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/CheckHealthCommandTest.java b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/CheckHealthCommandTest.java new file mode 100644 index 0000000000..be0df32d41 --- /dev/null +++ b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/CheckHealthCommandTest.java @@ -0,0 +1,43 @@ +// +// 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 org.apache.cloudstack.api.agent.test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.cloud.agent.api.CheckHealthCommand; + +import org.junit.Test; + +public class CheckHealthCommandTest { + CheckHealthCommand chc = new CheckHealthCommand(); + + @Test + public void testGetWait() { + int wait = chc.getWait(); + assertTrue(wait == 50); + } + + @Test + public void testExecuteInSequence() { + boolean b = chc.executeInSequence(); + assertFalse(b); + } +} diff --git a/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/CheckNetworkAnswerTest.java b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/CheckNetworkAnswerTest.java new file mode 100644 index 0000000000..0c3c6b61e8 --- /dev/null +++ b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/CheckNetworkAnswerTest.java @@ -0,0 +1,279 @@ +// +// 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 org.apache.cloudstack.api.agent.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import com.cloud.agent.api.CheckNetworkAnswer; +import com.cloud.agent.api.CheckNetworkCommand; +import com.cloud.agent.api.storage.ResizeVolumeCommand; +import com.cloud.agent.api.to.StorageFilerTO; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.storage.Storage; +import com.cloud.storage.StoragePool; +import com.cloud.storage.StoragePoolStatus; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +public class CheckNetworkAnswerTest { + CheckNetworkCommand cnc; + CheckNetworkAnswer cna; + + @Before + public void setUp() { + cnc = Mockito.mock(CheckNetworkCommand.class); + cna = new CheckNetworkAnswer(cnc, true, "details", true); + } + + @Test + public void testGetResult() { + boolean b = cna.getResult(); + assertTrue(b); + } + + @Test + public void testGetDetails() { + String d = cna.getDetails(); + assertTrue(d.equals("details")); + } + + @Test + public void testNeedReconnect() { + boolean b = cna.needReconnect(); + assertTrue(b); + } + + @Test + public void testExecuteInSequence() { + boolean b = cna.executeInSequence(); + assertFalse(b); + } + + public static class ResizeVolumeCommandTest { + + public StoragePool dummypool = new StoragePool() { + @Override + public long getId() { + return 1L; + }; + + @Override + public String getName() { + return "name"; + }; + + @Override + public String getUuid() { + return "bed9f83e-cac3-11e1-ac8a-0050568b007e"; + }; + + @Override + public Storage.StoragePoolType getPoolType() { + return Storage.StoragePoolType.Filesystem; + }; + + @Override + public Date getCreated() { + Date date = null; + try { + date = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss").parse("01/01/1970 12:12:12"); + } catch (ParseException e) { + e.printStackTrace(); + } + return date; + } + + @Override + public Date getUpdateTime() { + return new Date(); + }; + + @Override + public long getDataCenterId() { + return 0L; + }; + + @Override + public long getCapacityBytes() { + return 0L; + }; + + @Override + public long getUsedBytes() { + return 0L; + }; + + @Override + public Long getCapacityIops() { + return 0L; + }; + + @Override + public Long getClusterId() { + return 0L; + }; + + @Override + public String getHostAddress() { + return "hostAddress"; + }; + + @Override + public String getPath() { + return "path"; + }; + + @Override + public String getUserInfo() { + return "userInfo"; + }; + + @Override + public boolean isShared() { + return false; + }; + + @Override + public boolean isLocal() { + return false; + }; + + @Override + public StoragePoolStatus getStatus() { + return StoragePoolStatus.Up; + }; + + @Override + public int getPort() { + return 25; + }; + + @Override + public Long getPodId() { + return 0L; + } + + @Override + public String getStorageProviderName() { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isInMaintenance() { + // TODO Auto-generated method stub + return false; + } + + @Override + public Hypervisor.HypervisorType getHypervisor() { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + ; + }; + + Long newSize = 4194304L; + Long currentSize = 1048576L; + + ResizeVolumeCommand rv = new ResizeVolumeCommand("dummydiskpath", new StorageFilerTO(dummypool), currentSize, newSize, false, "vmName"); + + @Test + public void testExecuteInSequence() { + boolean b = rv.executeInSequence(); + assertFalse(b); + } + + @Test + public void testGetPath() { + String path = rv.getPath(); + assertTrue(path.equals("dummydiskpath")); + } + + @Test + public void testGetPoolUuid() { + String poolUuid = rv.getPoolUuid(); + assertTrue(poolUuid.equals("bed9f83e-cac3-11e1-ac8a-0050568b007e")); + } + + @Test + public void testGetPool() { + StorageFilerTO pool = rv.getPool(); + + Long id = pool.getId(); + Long expectedL = 1L; + assertEquals(expectedL, id); + + String uuid = pool.getUuid(); + assertTrue(uuid.equals("bed9f83e-cac3-11e1-ac8a-0050568b007e")); + + String host = pool.getHost(); + assertTrue(host.equals("hostAddress")); + + String path = pool.getPath(); + assertTrue(path.equals("path")); + + String userInfo = pool.getUserInfo(); + assertTrue(userInfo.equals("userInfo")); + + Integer port = pool.getPort(); + Integer expectedI = 25; + assertEquals(expectedI, port); + + Storage.StoragePoolType type = pool.getType(); + assertEquals(Storage.StoragePoolType.Filesystem, type); + + String str = pool.toString(); + assertTrue(str.equals("Pool[" + id.toString() + "|" + host + ":" + port.toString() + "|" + path + "]")); + } + + @Test + public void testGetNewSize() { + long newSize = rv.getNewSize(); + assertTrue(newSize == 4194304L); + } + + @Test + public void testGetCurrentSize() { + long currentSize = rv.getCurrentSize(); + assertTrue(currentSize == 1048576L); + } + + @Test + public void testGetShrinkOk() { + assertFalse(rv.getShrinkOk()); + } + + @Test + public void testGetInstanceName() { + String vmName = rv.getInstanceName(); + assertTrue(vmName.equals("vmName")); + } + + } +} diff --git a/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/CheckNetworkCommandTest.java b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/CheckNetworkCommandTest.java new file mode 100644 index 0000000000..9e36122511 --- /dev/null +++ b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/CheckNetworkCommandTest.java @@ -0,0 +1,55 @@ +// +// 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 org.apache.cloudstack.api.agent.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import com.cloud.agent.api.CheckNetworkCommand; +import com.cloud.network.PhysicalNetworkSetupInfo; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +public class CheckNetworkCommandTest { + CheckNetworkCommand cnc; + + @Before + public void setUp() { + @SuppressWarnings("unchecked") + List net = Mockito.mock(List.class); + cnc = new CheckNetworkCommand(net); + } + + @Test + public void testGetPhysicalNetworkInfoList() { + List networkInfoList = cnc.getPhysicalNetworkInfoList(); + assertEquals(0, networkInfoList.size()); + } + + @Test + public void testExecuteInSequence() { + boolean b = cnc.executeInSequence(); + assertTrue(b); + } +} diff --git a/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/CheckOnHostCommandTest.java b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/CheckOnHostCommandTest.java new file mode 100644 index 0000000000..8367aca32c --- /dev/null +++ b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/CheckOnHostCommandTest.java @@ -0,0 +1,531 @@ +// +// 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 org.apache.cloudstack.api.agent.test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import com.cloud.agent.api.CheckOnHostCommand; +import com.cloud.agent.api.to.HostTO; +import com.cloud.host.Host; +import com.cloud.host.Status; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.resource.ResourceState; + +import org.junit.Test; + +public class CheckOnHostCommandTest { + public Host host = new Host() { + @Override + public Status getState() { + return Status.Up; + }; + + @Override + public long getId() { + return 101L; + }; + + @Override + public String getUuid() { + return "101"; + } + + @Override + public String getName() { + return "hostName"; + }; + + @Override + public Type getType() { + return Host.Type.Storage; + }; + + @Override + public Date getCreated() { + Date date = null; + try { + date = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss").parse("01/01/1970 12:12:12"); + } catch (ParseException e) { + e.printStackTrace(); + } + return date; + } + + @Override + public Status getStatus() { + return Status.Up; + }; + + @Override + public String getPrivateIpAddress() { + return "10.1.1.1"; + }; + + public String getStorageUrl() { + return null; + } + + public String getStorageIpAddress() { + return "10.1.1.2"; + }; + + @Override + public String getGuid() { + return "bed9f83e-cac3-11e1-ac8a-0050568b007e"; + }; + + @Override + public Long getTotalMemory() { + return 100000000000L; + } + + @Override + public Integer getCpuSockets() { + return 1; + }; + + @Override + public Integer getCpus() { + return 16; + }; + + @Override + public Long getSpeed() { + return 2000000000L; + }; + + @Override + public Integer getProxyPort() { + return 22; + }; + + @Override + public Long getPodId() { + return 16L; + }; + + @Override + public long getDataCenterId() { + return 17L; + }; + + @Override + public String getParent() { + return "parent"; + }; + + @Override + public String getStorageIpAddressDeux() { + return "10.1.1.3"; + }; + + @Override + public HypervisorType getHypervisorType() { + return HypervisorType.XenServer; + }; + + @Override + public Date getDisconnectedOn() { + Date date = null; + try { + date = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss").parse("01/01/2012 12:12:12"); + } catch (ParseException e) { + e.printStackTrace(); + } + return date; + } + + @Override + public String getVersion() { + return "4.0.1"; + }; + + @Override + public long getTotalSize() { + return 100000000000L; + }; + + @Override + public String getCapabilities() { + return "capabilities"; + }; + + @Override + public long getLastPinged() { + return 1L; + }; + + @Override + public Long getManagementServerId() { + return 2L; + }; + + @Override + public Date getRemoved() { + Date date = null; + try { + date = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss").parse("02/01/2012 12:12:12"); + } catch (ParseException e) { + e.printStackTrace(); + } + return date; + }; + + @Override + public Long getClusterId() { + return 3L; + }; + + @Override + public String getPublicIpAddress() { + return "10.1.1.4"; + }; + + @Override + public String getPublicNetmask() { + return "255.255.255.8"; + }; + + @Override + public String getPrivateNetmask() { + return "255.255.255.16"; + }; + + @Override + public String getStorageNetmask() { + return "255.255.255.24"; + }; + + @Override + public String getStorageMacAddress() { + return "01:f4:17:38:0e:26"; + }; + + @Override + public String getPublicMacAddress() { + return "02:f4:17:38:0e:26"; + }; + + @Override + public String getPrivateMacAddress() { + return "03:f4:17:38:0e:26"; + }; + + @Override + public String getStorageNetmaskDeux() { + return "255.255.255.25"; + }; + + @Override + public String getStorageMacAddressDeux() { + return "01:f4:17:38:0e:27"; + }; + + @Override + public String getHypervisorVersion() { + return "1.2.3.0"; + }; + + @Override + public boolean isInMaintenanceStates() { + return false; + }; + + @Override + public ResourceState getResourceState() { + return ResourceState.Enabled; + }; + }; + + CheckOnHostCommand cohc = new CheckOnHostCommand(host); + + @Test + public void testGetHost() { + HostTO h = cohc.getHost(); + assertNotNull(h); + } + + @Test + public void testGetState() { + Status s = host.getState(); + assertTrue(s == Status.Up); + } + + @Test + public void testGetId() { + Long id = host.getId(); + assertTrue(101L == id); + } + + @Test + public void testGetName() { + String name = host.getName(); + assertTrue(name.equals("hostName")); + } + + @Test + public void testGetType() { + Host.Type t = host.getType(); + assertTrue(t == Host.Type.Storage); + } + + @Test + public void testGetCreated() { + try { + Date date = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss").parse("01/01/1970 12:12:12"); + Date d = host.getCreated(); + assertTrue(d.compareTo(date) == 0); + } catch (ParseException e) { + e.printStackTrace(); + } + } + + @Test + public void testGetStatus() { + Status s = host.getStatus(); + assertTrue(s == Status.Up); + } + + @Test + public void testGetPrivateIpAddress() { + String addr = host.getPrivateIpAddress(); + assertTrue(addr.equals("10.1.1.1")); + } + + @Test + public void testGetStorageIpAddress() { + String addr = host.getStorageIpAddress(); + assertTrue(addr.equals("10.1.1.2")); + } + + @Test + public void testGetGuid() { + String guid = host.getGuid(); + assertTrue(guid.equals("bed9f83e-cac3-11e1-ac8a-0050568b007e")); + } + + @Test + public void testGetTotalMemory() { + Long m = host.getTotalMemory(); + assertTrue(m == 100000000000L); + } + + @Test + public void testGetCpuSockets() { + Integer cpuSockets = host.getCpuSockets(); + assertTrue(cpuSockets == 1); + } + + @Test + public void testGetCpus() { + int cpus = host.getCpus(); + assertTrue(cpus == 16); + } + + @Test + public void testGetSpeed() { + Long spped = host.getSpeed(); + assertTrue(spped == 2000000000L); + } + + @Test + public void testGetProxyPort() { + Integer port = host.getProxyPort(); + assertTrue(port == 22); + } + + @Test + public void testGetPodId() { + Long pID = host.getPodId(); + assertTrue(pID == 16L); + } + + @Test + public void testGetDataCenterId() { + long dcID = host.getDataCenterId(); + assertTrue(dcID == 17L); + } + + @Test + public void testGetParent() { + String p = host.getParent(); + assertTrue(p.equals("parent")); + } + + @Test + public void testGetStorageIpAddressDeux() { + String addr = host.getStorageIpAddressDeux(); + assertTrue(addr.equals("10.1.1.3")); + } + + @Test + public void testGetHypervisorType() { + HypervisorType type = host.getHypervisorType(); + assertTrue(type == HypervisorType.XenServer); + } + + @Test + public void testGetDisconnectedOn() { + try { + Date date = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss").parse("01/01/2012 12:12:12"); + Date d = host.getDisconnectedOn(); + assertTrue(d.compareTo(date) == 0); + } catch (ParseException e) { + e.printStackTrace(); + } + } + + @Test + public void testGetVersion() { + String v = host.getVersion(); + assertTrue(v.equals("4.0.1")); + } + + @Test + public void testGetTotalSize() { + long size = host.getTotalSize(); + assertTrue(size == 100000000000L); + } + + @Test + public void testGetCapabilities() { + String c = host.getCapabilities(); + assertTrue(c.equals("capabilities")); + } + + @Test + public void testGetLastPinged() { + long lp = host.getLastPinged(); + assertTrue(lp == 1L); + } + + @Test + public void testGetManagementServerId() { + Long msID = host.getManagementServerId(); + assertTrue(msID == 2L); + } + + @Test + public void testGetRemoved() { + try { + Date date = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss").parse("02/01/2012 12:12:12"); + Date d = host.getRemoved(); + assertTrue(d.compareTo(date) == 0); + } catch (ParseException e) { + e.printStackTrace(); + } + } + + @Test + public void testGetClusterId() { + Long cID = host.getClusterId(); + assertTrue(cID == 3L); + } + + @Test + public void testGetPublicIpAddress() { + String pipAddr = host.getPublicIpAddress(); + assertTrue(pipAddr.equals("10.1.1.4")); + } + + @Test + public void testGetPublicNetmask() { + String pMask = host.getPublicNetmask(); + assertTrue(pMask.equals("255.255.255.8")); + } + + @Test + public void testGetPrivateNetmask() { + String pMask = host.getPrivateNetmask(); + assertTrue(pMask.equals("255.255.255.16")); + } + + @Test + public void testGetStorageNetmask() { + String sMask = host.getStorageNetmask(); + assertTrue(sMask.equals("255.255.255.24")); + } + + @Test + public void testGetStorageMacAddress() { + String sMac = host.getStorageMacAddress(); + assertTrue(sMac.equals("01:f4:17:38:0e:26")); + } + + @Test + public void testGetPublicMacAddress() { + String pMac = host.getPublicMacAddress(); + assertTrue(pMac.equals("02:f4:17:38:0e:26")); + } + + @Test + public void testGetPrivateMacAddress() { + String pMac = host.getPrivateMacAddress(); + assertTrue(pMac.equals("03:f4:17:38:0e:26")); + } + + @Test + public void testGetStorageNetmaskDeux() { + String sMask = host.getStorageNetmaskDeux(); + assertTrue(sMask.equals("255.255.255.25")); + } + + @Test + public void testGetStorageMacAddressDeux() { + String sMac = host.getStorageMacAddressDeux(); + assertTrue(sMac.equals("01:f4:17:38:0e:27")); + } + + @Test + public void testGetHypervisorVersion() { + String v = host.getHypervisorVersion(); + assertTrue(v.equals("1.2.3.0")); + } + + @Test + public void testIsInMaintenanceStates() { + boolean b = host.isInMaintenanceStates(); + assertFalse(b); + } + + @Test + public void testGetResourceState() { + ResourceState r = host.getResourceState(); + assertTrue(r == ResourceState.Enabled); + } + + @Test + public void testGetWait() { + int wait = cohc.getWait(); + assertTrue(20 == wait); + } + + @Test + public void testExecuteInSequence() { + boolean b = cohc.executeInSequence(); + assertFalse(b); + } +} diff --git a/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/SnapshotCommandTest.java b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/SnapshotCommandTest.java new file mode 100644 index 0000000000..3931521d36 --- /dev/null +++ b/cosmic-core/nucleo/src/test/java/org/apache/cloudstack/api/agent/test/SnapshotCommandTest.java @@ -0,0 +1,230 @@ +// +// 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 org.apache.cloudstack.api.agent.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import com.cloud.agent.api.SnapshotCommand; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.storage.Storage.StoragePoolType; +import com.cloud.storage.StoragePool; +import com.cloud.storage.StoragePoolStatus; + +import org.junit.Before; +import org.junit.Test; + +public class SnapshotCommandTest { + + public StoragePool pool = new StoragePool() { + @Override + public long getId() { + return 1L; + }; + + @Override + public String getName() { + return "name"; + }; + + @Override + public String getUuid() { + return "bed9f83e-cac3-11e1-ac8a-0050568b007e"; + }; + + @Override + public StoragePoolType getPoolType() { + return StoragePoolType.Filesystem; + }; + + @Override + public Date getCreated() { + Date date = null; + try { + date = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss").parse("01/01/1970 12:12:12"); + } catch (ParseException e) { + e.printStackTrace(); + } + return date; + } + + @Override + public Date getUpdateTime() { + return new Date(); + }; + + @Override + public long getDataCenterId() { + return 0L; + }; + + @Override + public long getCapacityBytes() { + return 0L; + }; + + @Override + public long getUsedBytes() { + return 0L; + }; + + @Override + public Long getCapacityIops() { + return 0L; + }; + + @Override + public Long getClusterId() { + return 0L; + }; + + @Override + public String getHostAddress() { + return "hostAddress"; + }; + + @Override + public String getPath() { + return "path"; + }; + + @Override + public String getUserInfo() { + return "userInfo"; + }; + + @Override + public boolean isShared() { + return false; + }; + + @Override + public boolean isLocal() { + return false; + }; + + @Override + public StoragePoolStatus getStatus() { + return StoragePoolStatus.Up; + }; + + @Override + public int getPort() { + return 25; + }; + + @Override + public Long getPodId() { + return 0L; + } + + @Override + public String getStorageProviderName() { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isInMaintenance() { + // TODO Auto-generated method stub + return false; + } + + @Override + public Hypervisor.HypervisorType getHypervisor() { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + ; + }; + + SnapshotCommand ssc = new SnapshotCommand(pool, "http://secondary.Storage.Url", "420fa39c-4ef1-a83c-fd93-46dc1ff515ae", "snapshotName", 101L, 102L, 103L); + + SnapshotCommand ssc1; + + @Before + public void setUp() { + ssc1 = new SnapshotCommand(pool, "secondaryStorageUrl", "snapshotUuid", "snapshotName", 101L, 102L, 103L); + } + + @Test + public void testGetSecondaryStorageUrl() { + String url = ssc.getSecondaryStorageUrl(); + assertTrue(url.equals("http://secondary.Storage.Url")); + } + + @Test + public void testGetSnapshotUuid() { + String uuid = ssc.getSnapshotUuid(); + assertTrue(uuid.equals("420fa39c-4ef1-a83c-fd93-46dc1ff515ae")); + } + + @Test + public void testGetSnapshotName() { + String name = ssc.getSnapshotName(); + assertTrue(name.equals("snapshotName")); + } + + @Test + public void testGetVolumePath() { + ssc.setVolumePath("vPath"); + String path = ssc.getVolumePath(); + assertTrue(path.equals("vPath")); + + ssc1.setVolumePath("vPath1"); + path = ssc1.getVolumePath(); + assertTrue(path.equals("vPath1")); + } + + @Test + public void testExecuteInSequence() { + boolean b = ssc.executeInSequence(); + assertFalse(b); + + b = ssc1.executeInSequence(); + assertFalse(b); + } + + @Test + public void testGetDataCenterId() { + Long dcId = ssc.getDataCenterId(); + Long expected = 101L; + assertEquals(expected, dcId); + } + + @Test + public void testGetAccountId() { + Long aId = ssc.getAccountId(); + Long expected = 102L; + assertEquals(expected, aId); + } + + @Test + public void testGetVolumeId() { + Long vId = ssc.getVolumeId(); + Long expected = 103L; + assertEquals(expected, vId); + } +} diff --git a/cosmic-core/plugins/acl/static-role-based/pom.xml b/cosmic-core/plugins/acl/static-role-based/pom.xml new file mode 100644 index 0000000000..c3f4c281b0 --- /dev/null +++ b/cosmic-core/plugins/acl/static-role-based/pom.xml @@ -0,0 +1,11 @@ + + 4.0.0 + cloud-plugin-acl-static-role-based + Cosmic Plugin - ACL Static Role Based + + cloud.cosmic + cosmic-plugins + 5.1.0.0-SNAPSHOT + ../../pom.xml + + \ No newline at end of file diff --git a/cosmic-core/plugins/acl/static-role-based/src/main/java/org/apache/cloudstack/acl/StaticRoleBasedAPIAccessChecker.java b/cosmic-core/plugins/acl/static-role-based/src/main/java/org/apache/cloudstack/acl/StaticRoleBasedAPIAccessChecker.java new file mode 100644 index 0000000000..bbfd043805 --- /dev/null +++ b/cosmic-core/plugins/acl/static-role-based/src/main/java/org/apache/cloudstack/acl/StaticRoleBasedAPIAccessChecker.java @@ -0,0 +1,141 @@ +// 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 org.apache.cloudstack.acl; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import com.cloud.exception.PermissionDeniedException; +import com.cloud.user.Account; +import com.cloud.user.AccountService; +import com.cloud.user.User; +import com.cloud.utils.PropertiesUtil; +import com.cloud.utils.component.AdapterBase; +import com.cloud.utils.component.PluggableService; + +import org.apache.cloudstack.api.APICommand; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// This is the default API access checker that grab's the user's account +// based on the account type, access is granted +public class StaticRoleBasedAPIAccessChecker extends AdapterBase implements APIChecker { + + protected static final Logger s_logger = LoggerFactory.getLogger(StaticRoleBasedAPIAccessChecker.class); + + Set commandPropertyFiles = new HashSet(); + Set commandsPropertiesOverrides = new HashSet(); + Map> commandsPropertiesRoleBasedApisMap = new HashMap>(); + Map> annotationRoleBasedApisMap = new HashMap>(); + + List _services; + @Inject + AccountService _accountService; + + protected StaticRoleBasedAPIAccessChecker() { + super(); + for (RoleType roleType : RoleType.values()) { + commandsPropertiesRoleBasedApisMap.put(roleType, new HashSet()); + annotationRoleBasedApisMap.put(roleType, new HashSet()); + } + } + + @Override + public boolean checkAccess(User user, String commandName) throws PermissionDeniedException { + Account account = _accountService.getAccount(user.getAccountId()); + if (account == null) { + throw new PermissionDeniedException("The account id=" + user.getAccountId() + "for user id=" + user.getId() + "is null"); + } + + RoleType roleType = _accountService.getRoleType(account); + boolean isAllowed = + commandsPropertiesOverrides.contains(commandName) ? commandsPropertiesRoleBasedApisMap.get(roleType).contains(commandName) : annotationRoleBasedApisMap.get( + roleType).contains(commandName); + + if (!isAllowed) { + throw new PermissionDeniedException("The API does not exist or is blacklisted. Role type=" + roleType.toString() + " is not allowed to request the api: " + + commandName); + } + return isAllowed; + } + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + super.configure(name, params); + + for (String commandPropertyFile : commandPropertyFiles) { + processMapping(PropertiesUtil.processConfigFile(new String[] { commandPropertyFile })); + } + return true; + } + + @Override + public boolean start() { + for (PluggableService service : _services) { + for (Class clz : service.getCommands()) { + APICommand command = clz.getAnnotation(APICommand.class); + for (RoleType role : command.authorized()) { + Set commands = annotationRoleBasedApisMap.get(role); + if (!commands.contains(command.name())) + commands.add(command.name()); + } + } + } + return super.start(); + } + + private void processMapping(Map configMap) { + for (Map.Entry entry : configMap.entrySet()) { + String apiName = entry.getKey(); + String roleMask = entry.getValue(); + commandsPropertiesOverrides.add(apiName); + try { + short cmdPermissions = Short.parseShort(roleMask); + for (RoleType roleType : RoleType.values()) { + if ((cmdPermissions & roleType.getValue()) != 0) + commandsPropertiesRoleBasedApisMap.get(roleType).add(apiName); + } + } catch (NumberFormatException nfe) { + s_logger.info("Malformed key=value pair for entry: " + entry.toString()); + } + } + } + + public List getServices() { + return _services; + } + + @Inject + public void setServices(List services) { + this._services = services; + } + + public Set getCommandPropertyFiles() { + return commandPropertyFiles; + } + + public void setCommandPropertyFiles(Set commandPropertyFiles) { + this.commandPropertyFiles = commandPropertyFiles; + } + +} diff --git a/cosmic-core/plugins/acl/static-role-based/src/main/resources/META-INF/cloudstack/acl-static-role-based/module.properties b/cosmic-core/plugins/acl/static-role-based/src/main/resources/META-INF/cloudstack/acl-static-role-based/module.properties new file mode 100644 index 0000000000..06fc721bc0 --- /dev/null +++ b/cosmic-core/plugins/acl/static-role-based/src/main/resources/META-INF/cloudstack/acl-static-role-based/module.properties @@ -0,0 +1,18 @@ +# 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. +name=acl-static-role-based +parent=api \ No newline at end of file diff --git a/cosmic-core/plugins/acl/static-role-based/src/main/resources/META-INF/cloudstack/acl-static-role-based/spring-acl-static-role-based-context.xml b/cosmic-core/plugins/acl/static-role-based/src/main/resources/META-INF/cloudstack/acl-static-role-based/spring-acl-static-role-based-context.xml new file mode 100644 index 0000000000..0b222837f4 --- /dev/null +++ b/cosmic-core/plugins/acl/static-role-based/src/main/resources/META-INF/cloudstack/acl-static-role-based/spring-acl-static-role-based-context.xml @@ -0,0 +1,39 @@ + + + + + + + + commands.properties + + + + + diff --git a/cosmic-core/plugins/affinity-group-processors/explicit-dedication/pom.xml b/cosmic-core/plugins/affinity-group-processors/explicit-dedication/pom.xml new file mode 100644 index 0000000000..e8d0381299 --- /dev/null +++ b/cosmic-core/plugins/affinity-group-processors/explicit-dedication/pom.xml @@ -0,0 +1,14 @@ + + 4.0.0 + cloud-plugin-explicit-dedication + Cosmic Plugin - Explicit Dedication Processor + + cloud.cosmic + cosmic-plugins + 5.1.0.0-SNAPSHOT + ../../pom.xml + + + install + + \ No newline at end of file diff --git a/cosmic-core/plugins/affinity-group-processors/explicit-dedication/src/main/java/org/apache/cloudstack/affinity/ExplicitDedicationProcessor.java b/cosmic-core/plugins/affinity-group-processors/explicit-dedication/src/main/java/org/apache/cloudstack/affinity/ExplicitDedicationProcessor.java new file mode 100644 index 0000000000..4c07b2af53 --- /dev/null +++ b/cosmic-core/plugins/affinity-group-processors/explicit-dedication/src/main/java/org/apache/cloudstack/affinity/ExplicitDedicationProcessor.java @@ -0,0 +1,442 @@ +// 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 org.apache.cloudstack.affinity; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import javax.inject.Inject; + +import com.cloud.dc.ClusterVO; +import com.cloud.dc.DataCenter; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.DedicatedResourceVO; +import com.cloud.dc.HostPodVO; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.dc.dao.DedicatedResourceDao; +import com.cloud.dc.dao.HostPodDao; +import com.cloud.deploy.DeploymentPlan; +import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.domain.DomainVO; +import com.cloud.domain.dao.DomainDao; +import com.cloud.exception.AffinityConflictException; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallbackNoReturn; +import com.cloud.utils.db.TransactionStatus; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.VMInstanceDao; + +import org.apache.cloudstack.affinity.dao.AffinityGroupDao; +import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ExplicitDedicationProcessor extends AffinityProcessorBase implements AffinityGroupProcessor { + + private static final Logger s_logger = LoggerFactory.getLogger(ExplicitDedicationProcessor.class); + @Inject + protected UserVmDao _vmDao; + @Inject + protected VMInstanceDao _vmInstanceDao; + @Inject + protected DataCenterDao _dcDao; + @Inject + protected DedicatedResourceDao _dedicatedDao; + @Inject + protected HostPodDao _podDao; + @Inject + protected ClusterDao _clusterDao; + @Inject + protected HostDao _hostDao; + @Inject + protected DomainDao _domainDao; + @Inject + protected AffinityGroupDao _affinityGroupDao; + @Inject + protected AffinityGroupVMMapDao _affinityGroupVMMapDao; + + /** + * This method will process the affinity group of type 'Explicit Dedication' for a deployment of a VM that demands dedicated resources. + * For ExplicitDedicationProcessor we need to add dedicated resources into the IncludeList based on the level we have dedicated resources available. + * For eg. if admin dedicates a pod to a domain, then all the user in that domain can use the resources of that pod. + * We need to take care of the situation when dedicated resources further have resources dedicated to sub-domain/account. + * This IncludeList is then used to update the avoid list for a given data center. + */ + @Override + public void process(VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid) throws AffinityConflictException { + VirtualMachine vm = vmProfile.getVirtualMachine(); + List vmGroupMappings = _affinityGroupVMMapDao.findByVmIdType(vm.getId(), getType()); + DataCenter dc = _dcDao.findById(vm.getDataCenterId()); + List resourceList = new ArrayList(); + + if (vmGroupMappings != null && !vmGroupMappings.isEmpty()) { + + for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) { + if (vmGroupMapping != null) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Processing affinity group " + vmGroupMapping.getAffinityGroupId() + "of type 'ExplicitDedication' for VM Id: " + vm.getId()); + } + + long affinityGroupId = vmGroupMapping.getAffinityGroupId(); + + List dr = _dedicatedDao.listByAffinityGroupId(affinityGroupId); + resourceList.addAll(dr); + + } + } + + boolean canUse = false; + + if (plan.getHostId() != null) { + HostVO host = _hostDao.findById(plan.getHostId()); + ClusterVO clusterofHost = _clusterDao.findById(host.getClusterId()); + HostPodVO podOfHost = _podDao.findById(host.getPodId()); + DataCenterVO zoneOfHost = _dcDao.findById(host.getDataCenterId()); + if (resourceList != null && resourceList.size() != 0) { + for (DedicatedResourceVO resource : resourceList) { + if ((resource.getHostId() != null && resource.getHostId().longValue() == plan.getHostId().longValue()) || + (resource.getClusterId() != null && resource.getClusterId().longValue() == clusterofHost.getId()) || + (resource.getPodId() != null && resource.getPodId().longValue() == podOfHost.getId()) || + (resource.getDataCenterId() != null && resource.getDataCenterId().longValue() == zoneOfHost.getId())) { + canUse = true; + } + } + } + if (!canUse) { + throw new CloudRuntimeException("Cannot use this host " + host.getName() + " for explicit dedication"); + } + } else if (plan.getClusterId() != null) { + ClusterVO cluster = _clusterDao.findById(plan.getClusterId()); + HostPodVO podOfCluster = _podDao.findById(cluster.getPodId()); + DataCenterVO zoneOfCluster = _dcDao.findById(cluster.getDataCenterId()); + List hostToUse = new ArrayList(); + // check whether this cluster or its pod is dedicated + if (resourceList != null && resourceList.size() != 0) { + for (DedicatedResourceVO resource : resourceList) { + if ((resource.getClusterId() != null && resource.getClusterId() == cluster.getId()) || + (resource.getPodId() != null && resource.getPodId() == podOfCluster.getId()) || + (resource.getDataCenterId() != null && resource.getDataCenterId() == zoneOfCluster.getId())) { + canUse = true; + } + + // check for all dedicated host; if it belongs to this + // cluster + if (!canUse) { + if (resource.getHostId() != null) { + HostVO dHost = _hostDao.findById(resource.getHostId()); + if (dHost.getClusterId() == cluster.getId()) { + hostToUse.add(dHost); + } + } + } + + } + } + + if (hostToUse.isEmpty() && !canUse) { + throw new CloudRuntimeException("Cannot use this cluster " + cluster.getName() + " for explicit dedication"); + } + + if (hostToUse != null && hostToUse.size() != 0) { + // add other non-dedicated hosts to avoid list + List hostList = _hostDao.findByClusterId(cluster.getId()); + for (HostVO host : hostList) { + if (!hostToUse.contains(host)) { + avoid.addHost(host.getId()); + } + } + } + + } else if (plan.getPodId() != null) { + HostPodVO pod = _podDao.findById(plan.getPodId()); + DataCenterVO zoneOfPod = _dcDao.findById(pod.getDataCenterId()); + List clustersToUse = new ArrayList(); + List hostsToUse = new ArrayList(); + // check whether this cluster or its pod is dedicated + if (resourceList != null && resourceList.size() != 0) { + for (DedicatedResourceVO resource : resourceList) { + if ((resource.getPodId() != null && resource.getPodId() == pod.getId()) || + (resource.getDataCenterId() != null && resource.getDataCenterId() == zoneOfPod.getId())) { + canUse = true; + } + + // check for all dedicated cluster/host; if it belongs + // to + // this pod + if (!canUse) { + if (resource.getClusterId() != null) { + ClusterVO dCluster = _clusterDao.findById(resource.getClusterId()); + if (dCluster.getPodId() == pod.getId()) { + clustersToUse.add(dCluster); + } + } + if (resource.getHostId() != null) { + HostVO dHost = _hostDao.findById(resource.getHostId()); + if (dHost.getPodId() == pod.getId()) { + hostsToUse.add(dHost); + } + } + } + + } + } + + if (hostsToUse.isEmpty() && clustersToUse.isEmpty() && !canUse) { + throw new CloudRuntimeException("Cannot use this pod " + pod.getName() + " for explicit dedication"); + } + + if (clustersToUse != null && clustersToUse.size() != 0) { + // add other non-dedicated clusters to avoid list + List clusterList = _clusterDao.listByPodId(pod.getId()); + for (ClusterVO cluster : clusterList) { + if (!clustersToUse.contains(cluster)) { + avoid.addCluster(cluster.getId()); + } + } + } + + if (hostsToUse != null && hostsToUse.size() != 0) { + // add other non-dedicated hosts to avoid list + List hostList = _hostDao.findByPodId(pod.getId()); + for (HostVO host : hostList) { + if (!hostsToUse.contains(host)) { + avoid.addHost(host.getId()); + } + } + } + + } else { + // check all resources under this zone + if (resourceList != null && resourceList.size() != 0) { + avoid = updateAvoidList(resourceList, avoid, dc); + } else { + avoid.addDataCenter(dc.getId()); + if (s_logger.isDebugEnabled()) { + s_logger.debug("No dedicated resources available for this domain or account under this group"); + } + } + + if (s_logger.isDebugEnabled()) { + s_logger.debug("ExplicitDedicationProcessor returns Avoid List as: Deploy avoids pods: " + avoid.getPodsToAvoid() + ", clusters: " + + avoid.getClustersToAvoid() + ", hosts: " + avoid.getHostsToAvoid()); + } + } + } + + } + + private ExcludeList updateAvoidList(List dedicatedResources, ExcludeList avoidList, DataCenter dc) { + ExcludeList includeList = new ExcludeList(); + for (DedicatedResourceVO dr : dedicatedResources) { + if (dr.getHostId() != null) { + includeList.addHost(dr.getHostId()); + HostVO dedicatedHost = _hostDao.findById(dr.getHostId()); + includeList.addCluster(dedicatedHost.getClusterId()); + includeList.addPod(dedicatedHost.getPodId()); + } + + if (dr.getClusterId() != null) { + includeList.addCluster(dr.getClusterId()); + //add all hosts inside this in includeList + List hostList = _hostDao.findByClusterId(dr.getClusterId()); + for (HostVO host : hostList) { + DedicatedResourceVO dHost = _dedicatedDao.findByHostId(host.getId()); + if (dHost != null && !dedicatedResources.contains(dHost)) { + avoidList.addHost(host.getId()); + } else { + includeList.addHost(host.getId()); + } + } + ClusterVO dedicatedCluster = _clusterDao.findById(dr.getClusterId()); + includeList.addPod(dedicatedCluster.getPodId()); + } + + if (dr.getPodId() != null) { + includeList.addPod(dr.getPodId()); + //add all cluster under this pod in includeList + List clusterList = _clusterDao.listByPodId(dr.getPodId()); + for (ClusterVO cluster : clusterList) { + DedicatedResourceVO dCluster = _dedicatedDao.findByClusterId(cluster.getId()); + if (dCluster != null && !dedicatedResources.contains(dCluster)) { + avoidList.addCluster(cluster.getId()); + } else { + includeList.addCluster(cluster.getId()); + } + } + //add all hosts inside this pod in includeList + List hostList = _hostDao.findByPodId(dr.getPodId()); + for (HostVO host : hostList) { + DedicatedResourceVO dHost = _dedicatedDao.findByHostId(host.getId()); + if (dHost != null && !dedicatedResources.contains(dHost)) { + avoidList.addHost(host.getId()); + } else { + includeList.addHost(host.getId()); + } + } + } + + if (dr.getDataCenterId() != null) { + includeList.addDataCenter(dr.getDataCenterId()); + //add all Pod under this data center in includeList + List podList = _podDao.listByDataCenterId(dr.getDataCenterId()); + for (HostPodVO pod : podList) { + DedicatedResourceVO dPod = _dedicatedDao.findByPodId(pod.getId()); + if (dPod != null && !dedicatedResources.contains(dPod)) { + avoidList.addPod(pod.getId()); + } else { + includeList.addPod(pod.getId()); + } + } + List clusterList = _clusterDao.listClustersByDcId(dr.getDataCenterId()); + for (ClusterVO cluster : clusterList) { + DedicatedResourceVO dCluster = _dedicatedDao.findByClusterId(cluster.getId()); + if (dCluster != null && !dedicatedResources.contains(dCluster)) { + avoidList.addCluster(cluster.getId()); + } else { + includeList.addCluster(cluster.getId()); + } + } + //add all hosts inside this in includeList + List hostList = _hostDao.listByDataCenterId(dr.getDataCenterId()); + for (HostVO host : hostList) { + DedicatedResourceVO dHost = _dedicatedDao.findByHostId(host.getId()); + if (dHost != null && !dedicatedResources.contains(dHost)) { + avoidList.addHost(host.getId()); + } else { + includeList.addHost(host.getId()); + } + } + } + } + //Update avoid list using includeList. + //add resources in avoid list which are not in include list. + + List pods = _podDao.listByDataCenterId(dc.getId()); + List clusters = _clusterDao.listClustersByDcId(dc.getId()); + List hosts = _hostDao.listByDataCenterId(dc.getId()); + Set podsInIncludeList = includeList.getPodsToAvoid(); + Set clustersInIncludeList = includeList.getClustersToAvoid(); + Set hostsInIncludeList = includeList.getHostsToAvoid(); + + for (HostPodVO pod : pods) { + if (podsInIncludeList != null && !podsInIncludeList.contains(pod.getId())) { + avoidList.addPod(pod.getId()); + } + } + + for (ClusterVO cluster : clusters) { + if (clustersInIncludeList != null && !clustersInIncludeList.contains(cluster.getId())) { + avoidList.addCluster(cluster.getId()); + } + } + + for (HostVO host : hosts) { + if (hostsInIncludeList != null && !hostsInIncludeList.contains(host.getId())) { + avoidList.addHost(host.getId()); + } + } + return avoidList; + } + + private List searchInParentDomainResources(long domainId) { + List domainIds = getDomainParentIds(domainId); + List dr = new ArrayList(); + for (Long id : domainIds) { + List resource = _dedicatedDao.listByDomainId(id); + if (resource != null) { + dr.addAll(resource); + } + } + return dr; + } + + private List searchInDomainResources(long domainId) { + List dr = _dedicatedDao.listByDomainId(domainId); + return dr; + } + + private List getDomainParentIds(long domainId) { + DomainVO domainRecord = _domainDao.findById(domainId); + List domainIds = new ArrayList(); + domainIds.add(domainRecord.getId()); + while (domainRecord.getParent() != null) { + domainRecord = _domainDao.findById(domainRecord.getParent()); + domainIds.add(domainRecord.getId()); + } + return domainIds; + } + + @Override + public boolean isAdminControlledGroup() { + return true; + } + + @Override + public boolean canBeSharedDomainWide() { + return true; + } + + @DB + @Override + public void handleDeleteGroup(final AffinityGroup group) { + // When a group of the 'ExplicitDedication' type gets deleted, make sure + // to remove the dedicated resources in the group as well. + if (group != null) { + List dedicatedResources = _dedicatedDao.listByAffinityGroupId(group.getId()); + if (!dedicatedResources.isEmpty()) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Releasing the dedicated resources under group: " + group); + } + + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + SearchBuilder listByAffinityGroup = _dedicatedDao.createSearchBuilder(); + listByAffinityGroup.and("affinityGroupId", listByAffinityGroup.entity().getAffinityGroupId(), SearchCriteria.Op.EQ); + listByAffinityGroup.done(); + SearchCriteria sc = listByAffinityGroup.create(); + sc.setParameters("affinityGroupId", group.getId()); + + _dedicatedDao.lockRows(sc, null, true); + _dedicatedDao.remove(sc); + } + }); + } else { + if (s_logger.isDebugEnabled()) { + s_logger.debug("No dedicated resources to releease under group: " + group); + } + } + } + + return; + } + + @Override + public boolean subDomainAccess() { + return true; + } +} diff --git a/cosmic-core/plugins/affinity-group-processors/explicit-dedication/src/main/resources/META-INF/cloudstack/explicit-dedication/module.properties b/cosmic-core/plugins/affinity-group-processors/explicit-dedication/src/main/resources/META-INF/cloudstack/explicit-dedication/module.properties new file mode 100644 index 0000000000..e204fe7ce4 --- /dev/null +++ b/cosmic-core/plugins/affinity-group-processors/explicit-dedication/src/main/resources/META-INF/cloudstack/explicit-dedication/module.properties @@ -0,0 +1,18 @@ +# 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. +name=explicit-dedication +parent=planner \ No newline at end of file diff --git a/cosmic-core/plugins/affinity-group-processors/explicit-dedication/src/main/resources/META-INF/cloudstack/explicit-dedication/spring-explicit-dedication-context.xml b/cosmic-core/plugins/affinity-group-processors/explicit-dedication/src/main/resources/META-INF/cloudstack/explicit-dedication/spring-explicit-dedication-context.xml new file mode 100644 index 0000000000..5864f94776 --- /dev/null +++ b/cosmic-core/plugins/affinity-group-processors/explicit-dedication/src/main/resources/META-INF/cloudstack/explicit-dedication/spring-explicit-dedication-context.xml @@ -0,0 +1,36 @@ + + + + + + + + + diff --git a/cosmic-core/plugins/affinity-group-processors/host-anti-affinity/pom.xml b/cosmic-core/plugins/affinity-group-processors/host-anti-affinity/pom.xml new file mode 100644 index 0000000000..5d77853f90 --- /dev/null +++ b/cosmic-core/plugins/affinity-group-processors/host-anti-affinity/pom.xml @@ -0,0 +1,14 @@ + + 4.0.0 + cloud-plugin-host-anti-affinity + Cosmic Plugin - Host Anti-Affinity Processor + + cloud.cosmic + cosmic-plugins + 5.1.0.0-SNAPSHOT + ../../pom.xml + + + install + + \ No newline at end of file diff --git a/cosmic-core/plugins/affinity-group-processors/host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java b/cosmic-core/plugins/affinity-group-processors/host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java new file mode 100644 index 0000000000..bb1e655d20 --- /dev/null +++ b/cosmic-core/plugins/affinity-group-processors/host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java @@ -0,0 +1,144 @@ +// 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 org.apache.cloudstack.affinity; + +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import com.cloud.configuration.Config; +import com.cloud.deploy.DeployDestination; +import com.cloud.deploy.DeploymentPlan; +import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.exception.AffinityConflictException; +import com.cloud.utils.DateUtil; +import com.cloud.utils.NumbersUtil; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.VMInstanceDao; + +import org.apache.cloudstack.affinity.dao.AffinityGroupDao; +import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; +import org.apache.cloudstack.engine.cloud.entity.api.db.VMReservationVO; +import org.apache.cloudstack.engine.cloud.entity.api.db.dao.VMReservationDao; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HostAntiAffinityProcessor extends AffinityProcessorBase implements AffinityGroupProcessor { + + private static final Logger s_logger = LoggerFactory.getLogger(HostAntiAffinityProcessor.class); + @Inject + protected UserVmDao _vmDao; + @Inject + protected VMInstanceDao _vmInstanceDao; + @Inject + protected AffinityGroupDao _affinityGroupDao; + @Inject + protected AffinityGroupVMMapDao _affinityGroupVMMapDao; + private int _vmCapacityReleaseInterval; + @Inject + protected ConfigurationDao _configDao; + + @Inject + protected VMReservationDao _reservationDao; + + @Override + public void process(VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid) throws AffinityConflictException { + VirtualMachine vm = vmProfile.getVirtualMachine(); + List vmGroupMappings = _affinityGroupVMMapDao.findByVmIdType(vm.getId(), getType()); + + for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) { + if (vmGroupMapping != null) { + AffinityGroupVO group = _affinityGroupDao.findById(vmGroupMapping.getAffinityGroupId()); + + if (s_logger.isDebugEnabled()) { + s_logger.debug("Processing affinity group " + group.getName() + " for VM Id: " + vm.getId()); + } + + List groupVMIds = _affinityGroupVMMapDao.listVmIdsByAffinityGroup(group.getId()); + groupVMIds.remove(vm.getId()); + + for (Long groupVMId : groupVMIds) { + VMInstanceVO groupVM = _vmInstanceDao.findById(groupVMId); + if (groupVM != null && !groupVM.isRemoved()) { + if (groupVM.getHostId() != null) { + avoid.addHost(groupVM.getHostId()); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Added host " + groupVM.getHostId() + " to avoid set, since VM " + groupVM.getId() + " is present on the host"); + } + } else if (VirtualMachine.State.Stopped.equals(groupVM.getState()) && groupVM.getLastHostId() != null) { + long secondsSinceLastUpdate = (DateUtil.currentGMTTime().getTime() - groupVM.getUpdateTime().getTime()) / 1000; + if (secondsSinceLastUpdate < _vmCapacityReleaseInterval) { + avoid.addHost(groupVM.getLastHostId()); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Added host " + groupVM.getLastHostId() + " to avoid set, since VM " + groupVM.getId() + + " is present on the host, in Stopped state but has reserved capacity"); + } + } + } + } + } + } + } + + } + + @Override + public boolean configure(final String name, final Map params) throws ConfigurationException { + super.configure(name, params); + _vmCapacityReleaseInterval = NumbersUtil.parseInt(_configDao.getValue(Config.CapacitySkipcountingHours.key()), 3600); + return true; + } + + @Override + public boolean check(VirtualMachineProfile vmProfile, DeployDestination plannedDestination) throws AffinityConflictException { + + if (plannedDestination.getHost() == null) { + return true; + } + long plannedHostId = plannedDestination.getHost().getId(); + + VirtualMachine vm = vmProfile.getVirtualMachine(); + + List vmGroupMappings = _affinityGroupVMMapDao.findByVmIdType(vm.getId(), getType()); + + for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) { + // if more than 1 VM's are present in the group then check for + // conflict due to parallel deployment + List groupVMIds = _affinityGroupVMMapDao.listVmIdsByAffinityGroup(vmGroupMapping.getAffinityGroupId()); + groupVMIds.remove(vm.getId()); + + for (Long groupVMId : groupVMIds) { + VMReservationVO vmReservation = _reservationDao.findByVmId(groupVMId); + if (vmReservation != null && vmReservation.getHostId() != null && vmReservation.getHostId().equals(plannedHostId)) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Planned destination for VM " + vm.getId() + " conflicts with an existing VM " + vmReservation.getVmId() + + " reserved on the same host " + plannedHostId); + } + return false; + } + } + } + return true; + } + +} diff --git a/cosmic-core/plugins/affinity-group-processors/host-anti-affinity/src/main/resources/META-INF/cloudstack/host-anti-affinity/module.properties b/cosmic-core/plugins/affinity-group-processors/host-anti-affinity/src/main/resources/META-INF/cloudstack/host-anti-affinity/module.properties new file mode 100644 index 0000000000..1ea1e8417f --- /dev/null +++ b/cosmic-core/plugins/affinity-group-processors/host-anti-affinity/src/main/resources/META-INF/cloudstack/host-anti-affinity/module.properties @@ -0,0 +1,18 @@ +# 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. +name=host-anti-affinity +parent=planner \ No newline at end of file diff --git a/cosmic-core/plugins/affinity-group-processors/host-anti-affinity/src/main/resources/META-INF/cloudstack/host-anti-affinity/spring-host-anti-affinity-context.xml b/cosmic-core/plugins/affinity-group-processors/host-anti-affinity/src/main/resources/META-INF/cloudstack/host-anti-affinity/spring-host-anti-affinity-context.xml new file mode 100644 index 0000000000..bc09cc3c60 --- /dev/null +++ b/cosmic-core/plugins/affinity-group-processors/host-anti-affinity/src/main/resources/META-INF/cloudstack/host-anti-affinity/spring-host-anti-affinity-context.xml @@ -0,0 +1,37 @@ + + + + + + + + + + diff --git a/cosmic-core/plugins/api/discovery/pom.xml b/cosmic-core/plugins/api/discovery/pom.xml new file mode 100644 index 0000000000..3a865b02df --- /dev/null +++ b/cosmic-core/plugins/api/discovery/pom.xml @@ -0,0 +1,37 @@ + + 4.0.0 + cloud-plugin-api-discovery + Cosmic Plugin - API Discovery + + cloud.cosmic + cosmic-plugins + 5.1.0.0-SNAPSHOT + ../../pom.xml + + + + cloud.cosmic + cloud-api + ${project.version} + + + cloud.cosmic + cloud-utils + ${project.version} + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + -Xmx1024m + + org/apache/cloudstack/discovery/integration/* + + + + + + \ No newline at end of file diff --git a/cosmic-core/plugins/api/discovery/src/main/java/org/apache/cloudstack/api/command/user/discovery/ListApisCmd.java b/cosmic-core/plugins/api/discovery/src/main/java/org/apache/cloudstack/api/command/user/discovery/ListApisCmd.java new file mode 100644 index 0000000000..665fc24164 --- /dev/null +++ b/cosmic-core/plugins/api/discovery/src/main/java/org/apache/cloudstack/api/command/user/discovery/ListApisCmd.java @@ -0,0 +1,76 @@ +// 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 org.apache.cloudstack.api.command.user.discovery; + +import javax.inject.Inject; + +import com.cloud.user.User; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.ApiDiscoveryResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.discovery.ApiDiscoveryService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@APICommand(name = "listApis", + responseObject = ApiDiscoveryResponse.class, + description = "lists all available apis on the server, provided by the Api Discovery plugin", + since = "4.1.0", + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false) +public class ListApisCmd extends BaseCmd { + + public static final Logger s_logger = LoggerFactory.getLogger(ListApisCmd.class.getName()); + private static final String s_name = "listapisresponse"; + + @Inject + ApiDiscoveryService _apiDiscoveryService; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "API name") + private String name; + + @Override + public void execute() throws ServerApiException { + if (_apiDiscoveryService != null) { + User user = CallContext.current().getCallingUser(); + ListResponse response = (ListResponse)_apiDiscoveryService.listApis(user, name); + if (response == null) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Api Discovery plugin was unable to find an api by that name or process any apis"); + } + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } + } + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + // no owner is needed for list command + return 0; + } +} diff --git a/cosmic-core/plugins/api/discovery/src/main/java/org/apache/cloudstack/api/response/ApiDiscoveryResponse.java b/cosmic-core/plugins/api/discovery/src/main/java/org/apache/cloudstack/api/response/ApiDiscoveryResponse.java new file mode 100644 index 0000000000..e0d0a6f1f8 --- /dev/null +++ b/cosmic-core/plugins/api/discovery/src/main/java/org/apache/cloudstack/api/response/ApiDiscoveryResponse.java @@ -0,0 +1,123 @@ +// 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 org.apache.cloudstack.api.response; + +import java.util.HashSet; +import java.util.Set; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +@SuppressWarnings("unused") +public class ApiDiscoveryResponse extends BaseResponse { + @SerializedName(ApiConstants.NAME) + @Param(description = "the name of the api command") + private String name; + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "description of the api") + private String description; + + @SerializedName(ApiConstants.SINCE) + @Param(description = "version of CloudStack the api was introduced in") + private String since; + + @SerializedName(ApiConstants.IS_ASYNC) + @Param(description = "true if api is asynchronous") + private Boolean isAsync; + + @SerializedName("related") + @Param(description = "comma separated related apis") + private String related; + + @SerializedName(ApiConstants.PARAMS) + @Param(description = "the list params the api accepts", responseObject = ApiParameterResponse.class) + private Set params; + + @SerializedName(ApiConstants.RESPONSE) + @Param(description = "api response fields", responseObject = ApiResponseResponse.class) + private Set apiResponse; + + @SerializedName(ApiConstants.TYPE) + @Param(description = "response field type") + private String type; + + public ApiDiscoveryResponse() { + params = new HashSet(); + apiResponse = new HashSet(); + isAsync = false; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + + public void setSince(String since) { + this.since = since; + } + + public String getSince() { + return since; + } + + public void setAsync(Boolean isAsync) { + this.isAsync = isAsync; + } + + public boolean getAsync() { + return isAsync; + } + + public String getRelated() { + return related; + } + + public void setRelated(String related) { + this.related = related; + } + + public Set getParams() { + return params; + } + + public void setParams(Set params) { + this.params = params; + } + + public void addParam(ApiParameterResponse param) { + this.params.add(param); + } + + public void addApiResponse(ApiResponseResponse apiResponse) { + this.apiResponse.add(apiResponse); + } +} diff --git a/cosmic-core/plugins/api/discovery/src/main/java/org/apache/cloudstack/api/response/ApiParameterResponse.java b/cosmic-core/plugins/api/discovery/src/main/java/org/apache/cloudstack/api/response/ApiParameterResponse.java new file mode 100644 index 0000000000..4a928d9f1c --- /dev/null +++ b/cosmic-core/plugins/api/discovery/src/main/java/org/apache/cloudstack/api/response/ApiParameterResponse.java @@ -0,0 +1,89 @@ +// 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 org.apache.cloudstack.api.response; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +public class ApiParameterResponse extends BaseResponse { + @SerializedName(ApiConstants.NAME) + @Param(description = "the name of the api parameter") + private String name; + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "description of the api parameter") + private String description; + + @SerializedName(ApiConstants.TYPE) + @Param(description = "parameter type") + private String type; + + @SerializedName(ApiConstants.LENGTH) + @Param(description = "length of the parameter") + private int length; + + @SerializedName(ApiConstants.REQUIRED) + @Param(description = "true if this parameter is required for the api request") + private Boolean required; + + @SerializedName(ApiConstants.SINCE) + @Param(description = "version of CloudStack the api was introduced in") + private String since; + + @SerializedName("related") + @Param(description = "comma separated related apis to get the parameter") + private String related; + + public ApiParameterResponse() { + } + + public void setName(String name) { + this.name = name; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setType(String type) { + this.type = type; + } + + public void setLength(int length) { + this.length = length; + } + + public void setRequired(Boolean required) { + this.required = required; + } + + public void setSince(String since) { + this.since = since; + } + + public String getRelated() { + return related; + } + + public void setRelated(String related) { + this.related = related; + } + +} diff --git a/cosmic-core/plugins/api/discovery/src/main/java/org/apache/cloudstack/api/response/ApiResponseResponse.java b/cosmic-core/plugins/api/discovery/src/main/java/org/apache/cloudstack/api/response/ApiResponseResponse.java new file mode 100644 index 0000000000..0bed6a6fbb --- /dev/null +++ b/cosmic-core/plugins/api/discovery/src/main/java/org/apache/cloudstack/api/response/ApiResponseResponse.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 org.apache.cloudstack.api.response; + +import java.util.HashSet; +import java.util.Set; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +public class ApiResponseResponse extends BaseResponse { + @SerializedName(ApiConstants.NAME) + @Param(description = "the name of the api response field") + private String name; + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "description of the api response field") + private String description; + + @SerializedName(ApiConstants.TYPE) + @Param(description = "response field type") + private String type; + + @SerializedName(ApiConstants.RESPONSE) + @Param(description = "api response fields") + private Set apiResponse; + + public void setName(String name) { + this.name = name; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setType(String type) { + this.type = type; + } + + public void addApiResponse(ApiResponseResponse childApiResponse) { + if (this.apiResponse == null) { + this.apiResponse = new HashSet(); + } + this.apiResponse.add(childApiResponse); + } +} diff --git a/cosmic-core/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryService.java b/cosmic-core/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryService.java new file mode 100644 index 0000000000..07dc6973ab --- /dev/null +++ b/cosmic-core/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryService.java @@ -0,0 +1,27 @@ +// 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 org.apache.cloudstack.discovery; + +import com.cloud.user.User; +import com.cloud.utils.component.PluggableService; + +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.response.ListResponse; + +public interface ApiDiscoveryService extends PluggableService { + ListResponse listApis(User user, String apiName); +} diff --git a/cosmic-core/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryServiceImpl.java b/cosmic-core/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryServiceImpl.java new file mode 100644 index 0000000000..548ed98a7f --- /dev/null +++ b/cosmic-core/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryServiceImpl.java @@ -0,0 +1,274 @@ +// 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 org.apache.cloudstack.discovery; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.inject.Inject; + +import com.cloud.serializer.Param; +import com.cloud.user.User; +import com.cloud.utils.ReflectUtil; +import com.cloud.utils.StringUtils; +import com.cloud.utils.component.ComponentLifecycleBase; +import com.cloud.utils.component.PluggableService; +import com.google.gson.annotations.SerializedName; + +import org.apache.cloudstack.acl.APIChecker; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.BaseAsyncCreateCmd; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.command.user.discovery.ListApisCmd; +import org.apache.cloudstack.api.response.ApiDiscoveryResponse; +import org.apache.cloudstack.api.response.ApiParameterResponse; +import org.apache.cloudstack.api.response.ApiResponseResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +@Component +public class ApiDiscoveryServiceImpl extends ComponentLifecycleBase implements ApiDiscoveryService { + private static final Logger s_logger = LoggerFactory.getLogger(ApiDiscoveryServiceImpl.class); + + List _apiAccessCheckers = null; + List _services = null; + private static Map s_apiNameDiscoveryResponseMap = null; + + protected ApiDiscoveryServiceImpl() { + super(); + } + + @Override + public boolean start() { + if (s_apiNameDiscoveryResponseMap == null) { + long startTime = System.nanoTime(); + s_apiNameDiscoveryResponseMap = new HashMap(); + Set> cmdClasses = new HashSet>(); + for (PluggableService service : _services) { + s_logger.debug(String.format("getting api commands of service: %s", service.getClass().getName())); + cmdClasses.addAll(service.getCommands()); + } + cmdClasses.addAll(this.getCommands()); + cacheResponseMap(cmdClasses); + long endTime = System.nanoTime(); + s_logger.info("Api Discovery Service: Annotation, docstrings, api relation graph processed in " + (endTime - startTime) / 1000000.0 + " ms"); + } + + return true; + } + + protected Map> cacheResponseMap(Set> cmdClasses) { + Map> responseApiNameListMap = new HashMap>(); + + for (Class cmdClass : cmdClasses) { + APICommand apiCmdAnnotation = cmdClass.getAnnotation(APICommand.class); + if (apiCmdAnnotation == null) { + apiCmdAnnotation = cmdClass.getSuperclass().getAnnotation(APICommand.class); + } + if (apiCmdAnnotation == null || !apiCmdAnnotation.includeInApiDoc() || apiCmdAnnotation.name().isEmpty()) { + continue; + } + + String apiName = apiCmdAnnotation.name(); + if (s_logger.isTraceEnabled()) { + s_logger.trace("Found api: " + apiName); + } + ApiDiscoveryResponse response = getCmdRequestMap(cmdClass, apiCmdAnnotation); + + String responseName = apiCmdAnnotation.responseObject().getName(); + if (!responseName.contains("SuccessResponse")) { + if (!responseApiNameListMap.containsKey(responseName)) { + responseApiNameListMap.put(responseName, new ArrayList()); + } + responseApiNameListMap.get(responseName).add(apiName); + } + response.setRelated(responseName); + + Field[] responseFields = apiCmdAnnotation.responseObject().getDeclaredFields(); + for (Field responseField : responseFields) { + ApiResponseResponse responseResponse = getFieldResponseMap(responseField); + response.addApiResponse(responseResponse); + } + + response.setObjectName("api"); + s_apiNameDiscoveryResponseMap.put(apiName, response); + } + + for (String apiName : s_apiNameDiscoveryResponseMap.keySet()) { + ApiDiscoveryResponse response = s_apiNameDiscoveryResponseMap.get(apiName); + Set processedParams = new HashSet(); + for (ApiParameterResponse param : response.getParams()) { + if (responseApiNameListMap.containsKey(param.getRelated())) { + List relatedApis = responseApiNameListMap.get(param.getRelated()); + param.setRelated(StringUtils.join(relatedApis, ",")); + } else { + param.setRelated(null); + } + processedParams.add(param); + } + response.setParams(processedParams); + + if (responseApiNameListMap.containsKey(response.getRelated())) { + List relatedApis = responseApiNameListMap.get(response.getRelated()); + relatedApis.remove(apiName); + response.setRelated(StringUtils.join(relatedApis, ",")); + } else { + response.setRelated(null); + } + s_apiNameDiscoveryResponseMap.put(apiName, response); + } + return responseApiNameListMap; + } + + private ApiResponseResponse getFieldResponseMap(Field responseField) { + ApiResponseResponse responseResponse = new ApiResponseResponse(); + SerializedName serializedName = responseField.getAnnotation(SerializedName.class); + Param param = responseField.getAnnotation(Param.class); + if (serializedName != null && param != null) { + responseResponse.setName(serializedName.value()); + responseResponse.setDescription(param.description()); + responseResponse.setType(responseField.getType().getSimpleName().toLowerCase()); + //If response is not of primitive type - we have a nested entity + Class fieldClass = param.responseObject(); + if (fieldClass != null) { + Class superClass = fieldClass.getSuperclass(); + if (superClass != null) { + String superName = superClass.getName(); + if (superName.equals(BaseResponse.class.getName())) { + Field[] fields = fieldClass.getDeclaredFields(); + for (Field field : fields) { + ApiResponseResponse innerResponse = getFieldResponseMap(field); + if (innerResponse != null) { + responseResponse.addApiResponse(innerResponse); + } + } + } + } + } + } + return responseResponse; + } + + private ApiDiscoveryResponse getCmdRequestMap(Class cmdClass, APICommand apiCmdAnnotation) { + String apiName = apiCmdAnnotation.name(); + ApiDiscoveryResponse response = new ApiDiscoveryResponse(); + response.setName(apiName); + response.setDescription(apiCmdAnnotation.description()); + if (!apiCmdAnnotation.since().isEmpty()) { + response.setSince(apiCmdAnnotation.since()); + } + + Set fields = ReflectUtil.getAllFieldsForClass(cmdClass, new Class[] {BaseCmd.class, BaseAsyncCmd.class, BaseAsyncCreateCmd.class}); + + boolean isAsync = ReflectUtil.isCmdClassAsync(cmdClass, new Class[] {BaseAsyncCmd.class, BaseAsyncCreateCmd.class}); + + response.setAsync(isAsync); + + for (Field field : fields) { + Parameter parameterAnnotation = field.getAnnotation(Parameter.class); + if (parameterAnnotation != null && parameterAnnotation.expose() && parameterAnnotation.includeInApiDoc()) { + + ApiParameterResponse paramResponse = new ApiParameterResponse(); + paramResponse.setName(parameterAnnotation.name()); + paramResponse.setDescription(parameterAnnotation.description()); + paramResponse.setType(parameterAnnotation.type().toString().toLowerCase()); + paramResponse.setLength(parameterAnnotation.length()); + paramResponse.setRequired(parameterAnnotation.required()); + if (!parameterAnnotation.since().isEmpty()) { + paramResponse.setSince(parameterAnnotation.since()); + } + paramResponse.setRelated(parameterAnnotation.entityType()[0].getName()); + response.addParam(paramResponse); + } + } + return response; + } + + @Override + public ListResponse listApis(User user, String name) { + ListResponse response = new ListResponse(); + List responseList = new ArrayList(); + + if (user == null) + return null; + + if (name != null) { + if (!s_apiNameDiscoveryResponseMap.containsKey(name)) + return null; + + for (APIChecker apiChecker : _apiAccessCheckers) { + try { + apiChecker.checkAccess(user, name); + } catch (Exception ex) { + s_logger.debug("API discovery access check failed for " + name + " with " + ex.getMessage()); + return null; + } + } + responseList.add(s_apiNameDiscoveryResponseMap.get(name)); + + } else { + for (String apiName : s_apiNameDiscoveryResponseMap.keySet()) { + boolean isAllowed = true; + for (APIChecker apiChecker : _apiAccessCheckers) { + try { + apiChecker.checkAccess(user, apiName); + } catch (Exception ex) { + isAllowed = false; + } + } + if (isAllowed) + responseList.add(s_apiNameDiscoveryResponseMap.get(apiName)); + } + } + response.setResponses(responseList); + return response; + } + + @Override + public List> getCommands() { + List> cmdList = new ArrayList>(); + cmdList.add(ListApisCmd.class); + return cmdList; + } + + public List getApiAccessCheckers() { + return _apiAccessCheckers; + } + + public void setApiAccessCheckers(List apiAccessCheckers) { + this._apiAccessCheckers = apiAccessCheckers; + } + + public List getServices() { + return _services; + } + + @Inject + public void setServices(List services) { + this._services = services; + } +} diff --git a/cosmic-core/plugins/api/discovery/src/test/java/org/apache/cloudstack/discovery/ApiDiscoveryTest.java b/cosmic-core/plugins/api/discovery/src/test/java/org/apache/cloudstack/discovery/ApiDiscoveryTest.java new file mode 100644 index 0000000000..c905ce0be5 --- /dev/null +++ b/cosmic-core/plugins/api/discovery/src/test/java/org/apache/cloudstack/discovery/ApiDiscoveryTest.java @@ -0,0 +1,105 @@ +// 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 org.apache.cloudstack.discovery; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.naming.ConfigurationException; + +import com.cloud.user.User; +import com.cloud.user.UserVO; +import com.cloud.utils.component.PluggableService; + +import org.apache.cloudstack.acl.APIChecker; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.command.user.discovery.ListApisCmd; +import org.apache.cloudstack.api.response.ApiDiscoveryResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.junit.BeforeClass; +import org.junit.Test; + +public class ApiDiscoveryTest { + private static APIChecker s_apiChecker = mock(APIChecker.class); + private static PluggableService s_pluggableService = mock(PluggableService.class); + private static ApiDiscoveryServiceImpl s_discoveryService = new ApiDiscoveryServiceImpl(); + + private static Class testCmdClass = ListApisCmd.class; + private static User testUser; + private static String testApiName; + private static String testApiDescription; + private static String testApiSince; + private static boolean testApiAsync; + + @BeforeClass + public static void setUp() throws ConfigurationException { + testApiName = testCmdClass.getAnnotation(APICommand.class).name(); + testApiDescription = testCmdClass.getAnnotation(APICommand.class).description(); + testApiSince = testCmdClass.getAnnotation(APICommand.class).since(); + testApiAsync = false; + testUser = new UserVO(); + + s_discoveryService._apiAccessCheckers = mock(List.class); + s_discoveryService._services = mock(List.class); + + when(s_apiChecker.checkAccess(any(User.class), anyString())).thenReturn(true); + when(s_pluggableService.getCommands()).thenReturn(new ArrayList>()); + when(s_discoveryService._apiAccessCheckers.iterator()).thenReturn(Arrays.asList(s_apiChecker).iterator()); + when(s_discoveryService._services.iterator()).thenReturn(Arrays.asList(s_pluggableService).iterator()); + + Set> cmdClasses = new HashSet>(); + cmdClasses.add(ListApisCmd.class); + s_discoveryService.start(); + s_discoveryService.cacheResponseMap(cmdClasses); + } + + @Test + public void verifyListSingleApi() throws Exception { + ListResponse responses = (ListResponse)s_discoveryService.listApis(testUser, testApiName); + if (responses != null) { + ApiDiscoveryResponse response = responses.getResponses().get(0); + assertTrue("No. of response items should be one", responses.getCount() == 1); + assertEquals("Error in api name", testApiName, response.getName()); + assertEquals("Error in api description", testApiDescription, response.getDescription()); + assertEquals("Error in api since", testApiSince, response.getSince()); + assertEquals("Error in api isAsync", testApiAsync, response.getAsync()); + } + } + + @Test + public void verifyListApis() throws Exception { + ListResponse responses = (ListResponse)s_discoveryService.listApis(testUser, null); + if (responses != null) { + assertTrue("No. of response items > 1", responses.getCount().intValue() == 1); + for (ApiDiscoveryResponse response : responses.getResponses()) { + assertFalse("API name is empty", response.getName().isEmpty()); + assertFalse("API description is empty", response.getDescription().isEmpty()); + } + } + } +} diff --git a/cosmic-core/plugins/api/rate-limit/pom.xml b/cosmic-core/plugins/api/rate-limit/pom.xml new file mode 100644 index 0000000000..8fb5087468 --- /dev/null +++ b/cosmic-core/plugins/api/rate-limit/pom.xml @@ -0,0 +1,26 @@ + + 4.0.0 + cloud-plugin-api-limit-account-based + Cosmic Plugin - API Rate Limit + + cloud.cosmic + cosmic-plugins + 5.1.0.0-SNAPSHOT + ../../pom.xml + + + + + org.apache.maven.plugins + maven-surefire-plugin + + always + -Xmx2048m -XX:MaxPermSize=1024m + + org/apache/cloudstack/ratelimit/integration/* + + + + + + \ No newline at end of file diff --git a/cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/api/command/admin/ratelimit/ResetApiLimitCmd.java b/cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/api/command/admin/ratelimit/ResetApiLimitCmd.java new file mode 100644 index 0000000000..a08b6bc3c1 --- /dev/null +++ b/cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/api/command/admin/ratelimit/ResetApiLimitCmd.java @@ -0,0 +1,106 @@ +// 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 org.apache.cloudstack.api.command.admin.ratelimit; + +import javax.inject.Inject; + +import com.cloud.configuration.Config; +import com.cloud.user.Account; + +import org.apache.cloudstack.api.ACL; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.AccountResponse; +import org.apache.cloudstack.api.response.ApiLimitResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.ratelimit.ApiRateLimitService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@APICommand(name = "resetApiLimit", responseObject = ApiLimitResponse.class, description = "Reset api count", + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class ResetApiLimitCmd extends BaseCmd { + private static final Logger s_logger = LoggerFactory.getLogger(ResetApiLimitCmd.class.getName()); + + private static final String s_name = "resetapilimitresponse"; + + @Inject + ApiRateLimitService _apiLimitService; + + @Inject + ConfigurationDao _configDao; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @ACL + @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.UUID, entityType = AccountResponse.class, description = "the ID of the acount whose limit to be reset") + private Long accountId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getAccountId() { + return accountId; + } + + public void setAccountId(Long accountId) { + this.accountId = accountId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + Account account = CallContext.current().getCallingAccount(); + if (account != null) { + return account.getId(); + } + + return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked + } + + @Override + public void execute() { + boolean apiLimitEnabled = Boolean.parseBoolean(_configDao.getValue(Config.ApiLimitEnabled.key())); + if (!apiLimitEnabled) { + throw new ServerApiException(ApiErrorCode.UNSUPPORTED_ACTION_ERROR, "This api is only available when api.throttling.enabled = true."); + } + boolean result = _apiLimitService.resetApiLimit(this.accountId); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to reset api limit counter"); + } + } +} diff --git a/cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/api/command/user/ratelimit/GetApiLimitCmd.java b/cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/api/command/user/ratelimit/GetApiLimitCmd.java new file mode 100644 index 0000000000..9755e826f0 --- /dev/null +++ b/cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/api/command/user/ratelimit/GetApiLimitCmd.java @@ -0,0 +1,79 @@ +// 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 org.apache.cloudstack.api.command.user.ratelimit; + +import javax.inject.Inject; + +import com.cloud.configuration.Config; +import com.cloud.user.Account; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.ApiLimitResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.ratelimit.ApiRateLimitService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@APICommand(name = "getApiLimit", responseObject = ApiLimitResponse.class, description = "Get API limit count for the caller", + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class GetApiLimitCmd extends BaseCmd { + private static final Logger s_logger = LoggerFactory.getLogger(GetApiLimitCmd.class.getName()); + + private static final String s_name = "getapilimitresponse"; + + @Inject + ApiRateLimitService _apiLimitService; + + @Inject + ConfigurationDao _configDao; + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + Account account = CallContext.current().getCallingAccount(); + if (account != null) { + return account.getId(); + } + + return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked + } + + @Override + public void execute() { + boolean apiLimitEnabled = Boolean.parseBoolean(_configDao.getValue(Config.ApiLimitEnabled.key())); + if (!apiLimitEnabled) { + throw new ServerApiException(ApiErrorCode.UNSUPPORTED_ACTION_ERROR, "This api is only available when api.throttling.enabled = true."); + } + Account caller = CallContext.current().getCallingAccount(); + ApiLimitResponse response = _apiLimitService.searchApiLimit(caller); + response.setResponseName(getCommandName()); + response.setObjectName("apilimit"); + this.setResponseObject(response); + } +} diff --git a/cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/api/response/ApiLimitResponse.java b/cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/api/response/ApiLimitResponse.java new file mode 100644 index 0000000000..aa3407f9a0 --- /dev/null +++ b/cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/api/response/ApiLimitResponse.java @@ -0,0 +1,86 @@ +// 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 org.apache.cloudstack.api.response; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +public class ApiLimitResponse extends BaseResponse { + @SerializedName(ApiConstants.ACCOUNT_ID) + @Param(description = "the account uuid of the api remaining count") + private String accountId; + + @SerializedName(ApiConstants.ACCOUNT) + @Param(description = "the account name of the api remaining count") + private String accountName; + + @SerializedName("apiIssued") + @Param(description = "number of api already issued") + private int apiIssued; + + @SerializedName("apiAllowed") + @Param(description = "currently allowed number of apis") + private int apiAllowed; + + @SerializedName("expireAfter") + @Param(description = "seconds left to reset counters") + private long expireAfter; + + public void setAccountId(String accountId) { + this.accountId = accountId; + } + + public void setAccountName(String accountName) { + this.accountName = accountName; + } + + public void setApiIssued(int apiIssued) { + this.apiIssued = apiIssued; + } + + public void setApiAllowed(int apiAllowed) { + this.apiAllowed = apiAllowed; + } + + public void setExpireAfter(long duration) { + this.expireAfter = duration; + } + + public String getAccountId() { + return accountId; + } + + public String getAccountName() { + return accountName; + } + + public int getApiIssued() { + return apiIssued; + } + + public int getApiAllowed() { + return apiAllowed; + } + + public long getExpireAfter() { + return expireAfter; + } + +} diff --git a/cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/ApiRateLimitService.java b/cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/ApiRateLimitService.java new file mode 100644 index 0000000000..10559275a5 --- /dev/null +++ b/cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/ApiRateLimitService.java @@ -0,0 +1,39 @@ +// 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 org.apache.cloudstack.ratelimit; + +import com.cloud.user.Account; +import com.cloud.utils.component.PluggableService; + +import org.apache.cloudstack.api.response.ApiLimitResponse; + +/** + * Provide API rate limit service + * + */ +public interface ApiRateLimitService extends PluggableService { + + public ApiLimitResponse searchApiLimit(Account caller); + + public boolean resetApiLimit(Long accountId); + + public void setTimeToLive(int timeToLive); + + public void setMaxAllowed(int max); + + public void setEnabled(boolean enabled); +} diff --git a/cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/ApiRateLimitServiceImpl.java b/cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/ApiRateLimitServiceImpl.java new file mode 100644 index 0000000000..b31cced46c --- /dev/null +++ b/cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/ApiRateLimitServiceImpl.java @@ -0,0 +1,202 @@ +// 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 org.apache.cloudstack.ratelimit; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import com.cloud.configuration.Config; +import com.cloud.exception.PermissionDeniedException; +import com.cloud.exception.RequestLimitException; +import com.cloud.user.Account; +import com.cloud.user.AccountService; +import com.cloud.user.User; +import com.cloud.utils.component.AdapterBase; + +import org.apache.cloudstack.acl.APIChecker; +import org.apache.cloudstack.api.command.admin.ratelimit.ResetApiLimitCmd; +import org.apache.cloudstack.api.command.user.ratelimit.GetApiLimitCmd; +import org.apache.cloudstack.api.response.ApiLimitResponse; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import net.sf.ehcache.Cache; +import net.sf.ehcache.CacheManager; + +@Component +public class ApiRateLimitServiceImpl extends AdapterBase implements APIChecker, ApiRateLimitService { + private static final Logger s_logger = LoggerFactory.getLogger(ApiRateLimitServiceImpl.class); + + /** + * True if api rate limiting is enabled + */ + private boolean enabled = false; + + /** + * Fixed time duration where api rate limit is set, in seconds + */ + private int timeToLive = 1; + + /** + * Max number of api requests during timeToLive duration. + */ + private int maxAllowed = 30; + + private LimitStore _store = null; + + @Inject + AccountService _accountService; + + @Inject + ConfigurationDao _configDao; + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + super.configure(name, params); + + if (_store == null) { + // get global configured duration and max values + String isEnabled = _configDao.getValue(Config.ApiLimitEnabled.key()); + if (isEnabled != null) { + enabled = Boolean.parseBoolean(isEnabled); + } + String duration = _configDao.getValue(Config.ApiLimitInterval.key()); + if (duration != null) { + timeToLive = Integer.parseInt(duration); + } + String maxReqs = _configDao.getValue(Config.ApiLimitMax.key()); + if (maxReqs != null) { + maxAllowed = Integer.parseInt(maxReqs); + } + // create limit store + EhcacheLimitStore cacheStore = new EhcacheLimitStore(); + int maxElements = 10000; + String cachesize = _configDao.getValue(Config.ApiLimitCacheSize.key()); + if (cachesize != null) { + maxElements = Integer.parseInt(cachesize); + } + CacheManager cm = CacheManager.create(); + Cache cache = new Cache("api-limit-cache", maxElements, false, false, timeToLive, timeToLive); + cm.addCache(cache); + s_logger.info("Limit Cache created with timeToLive=" + timeToLive + ", maxAllowed=" + maxAllowed + ", maxElements=" + maxElements); + cacheStore.setCache(cache); + _store = cacheStore; + + } + + return true; + } + + @Override + public ApiLimitResponse searchApiLimit(Account caller) { + ApiLimitResponse response = new ApiLimitResponse(); + response.setAccountId(caller.getUuid()); + response.setAccountName(caller.getAccountName()); + StoreEntry entry = _store.get(caller.getId()); + if (entry == null) { + + /* Populate the entry, thus unlocking any underlying mutex */ + entry = _store.create(caller.getId(), timeToLive); + response.setApiIssued(0); + response.setApiAllowed(maxAllowed); + response.setExpireAfter(timeToLive); + } else { + response.setApiIssued(entry.getCounter()); + response.setApiAllowed(maxAllowed - entry.getCounter()); + response.setExpireAfter(entry.getExpireDuration()); + } + + return response; + } + + @Override + public boolean resetApiLimit(Long accountId) { + if (accountId != null) { + _store.create(accountId, timeToLive); + } else { + _store.resetCounters(); + } + return true; + } + + @Override + public boolean checkAccess(User user, String apiCommandName) throws PermissionDeniedException { + // check if api rate limiting is enabled or not + if (!enabled) { + return true; + } + Long accountId = user.getAccountId(); + Account account = _accountService.getAccount(accountId); + if (_accountService.isRootAdmin(account.getId())) { + // no API throttling on root admin + return true; + } + StoreEntry entry = _store.get(accountId); + + if (entry == null) { + + /* Populate the entry, thus unlocking any underlying mutex */ + entry = _store.create(accountId, timeToLive); + } + + /* Increment the client count and see whether we have hit the maximum allowed clients yet. */ + int current = entry.incrementAndGet(); + + if (current <= maxAllowed) { + s_logger.trace("account (" + account.getAccountId() + "," + account.getAccountName() + ") has current count = " + current); + return true; + } else { + long expireAfter = entry.getExpireDuration(); + // for this exception, we can just show the same message to user and admin users. + String msg = "The given user has reached his/her account api limit, please retry after " + expireAfter + " ms."; + s_logger.warn(msg); + throw new RequestLimitException(msg); + } + } + + @Override + public List> getCommands() { + List> cmdList = new ArrayList>(); + cmdList.add(ResetApiLimitCmd.class); + cmdList.add(GetApiLimitCmd.class); + return cmdList; + } + + @Override + public void setTimeToLive(int timeToLive) { + this.timeToLive = timeToLive; + } + + @Override + public void setMaxAllowed(int max) { + maxAllowed = max; + + } + + @Override + public void setEnabled(boolean enabled) { + this.enabled = enabled; + + } + +} diff --git a/cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/EhcacheLimitStore.java b/cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/EhcacheLimitStore.java new file mode 100644 index 0000000000..3840c49ba1 --- /dev/null +++ b/cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/EhcacheLimitStore.java @@ -0,0 +1,91 @@ +// 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 org.apache.cloudstack.ratelimit; + +import net.sf.ehcache.Ehcache; +import net.sf.ehcache.Element; +import net.sf.ehcache.constructs.blocking.BlockingCache; +import net.sf.ehcache.constructs.blocking.LockTimeoutException; + +/** + * A Limit store implementation using Ehcache. + * + */ +public class EhcacheLimitStore implements LimitStore { + + private BlockingCache cache; + + public void setCache(Ehcache cache) { + BlockingCache ref; + + if (!(cache instanceof BlockingCache)) { + ref = new BlockingCache(cache); + cache.getCacheManager().replaceCacheWithDecoratedCache(cache, new BlockingCache(cache)); + } else { + ref = (BlockingCache)cache; + } + + this.cache = ref; + } + + @Override + public StoreEntry create(Long key, int timeToLive) { + StoreEntryImpl result = new StoreEntryImpl(timeToLive); + Element element = new Element(key, result); + element.setTimeToLive(timeToLive); + cache.put(element); + return result; + } + + @Override + public StoreEntry get(Long key) { + + Element entry = null; + + try { + + /* This may block. */ + entry = cache.get(key); + } catch (LockTimeoutException e) { + throw new RuntimeException(); + } catch (RuntimeException e) { + + /* Release the lock that may have been acquired. */ + cache.put(new Element(key, null)); + } + + StoreEntry result = null; + + if (entry != null) { + + /* + * We don't need to check isExpired() on the result, since ehcache takes care of expiring entries for us. + * c.f. the get(Key) implementation in this class. + */ + result = (StoreEntry)entry.getObjectValue(); + } + + return result; + } + + @Override + public void resetCounters() { + cache.removeAll(); + + } + +} diff --git a/cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/LimitStore.java b/cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/LimitStore.java new file mode 100644 index 0000000000..f564b389df --- /dev/null +++ b/cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/LimitStore.java @@ -0,0 +1,48 @@ +// 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 org.apache.cloudstack.ratelimit; + +/** + * Interface to define how an api limit store should work. + * + */ +public interface LimitStore { + + /** + * Returns a store entry for the given account. A value of null means that there is no + * such entry and the calling client must call create to avoid + * other clients potentially being blocked without any hope of progressing. A non-null + * entry means that it has not expired and can be used to determine whether the current client should be allowed to + * proceed with the rate-limited action or not. + * + */ + StoreEntry get(Long account); + + /** + * Creates a new store entry + * + * @param account + * the user account, key to the store + * @param timeToLiveInSecs + * the positive time-to-live in seconds + * @return a non-null entry + */ + StoreEntry create(Long account, int timeToLiveInSecs); + + void resetCounters(); + +} diff --git a/cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/StoreEntry.java b/cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/StoreEntry.java new file mode 100644 index 0000000000..ce493ac179 --- /dev/null +++ b/cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/StoreEntry.java @@ -0,0 +1,32 @@ +// 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 org.apache.cloudstack.ratelimit; + +/** + * Interface for each entry in LimitStore. + * + */ +public interface StoreEntry { + + int getCounter(); + + int incrementAndGet(); + + boolean isExpired(); + + long getExpireDuration(); /* seconds to reset counter */ +} diff --git a/cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/StoreEntryImpl.java b/cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/StoreEntryImpl.java new file mode 100644 index 0000000000..f870489fb4 --- /dev/null +++ b/cosmic-core/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/StoreEntryImpl.java @@ -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. +package org.apache.cloudstack.ratelimit; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Implementation of limit store entry. + * + */ +public class StoreEntryImpl implements StoreEntry { + + private final long expiry; + + private final AtomicInteger counter; + + StoreEntryImpl(int timeToLive) { + this.expiry = System.currentTimeMillis() + timeToLive * 1000; + this.counter = new AtomicInteger(0); + } + + @Override + public boolean isExpired() { + return System.currentTimeMillis() > expiry; + } + + @Override + public long getExpireDuration() { + if (isExpired()) + return 0; // already expired + else { + return expiry - System.currentTimeMillis(); + } + } + + @Override + public int incrementAndGet() { + return this.counter.incrementAndGet(); + } + + @Override + public int getCounter() { + return this.counter.get(); + } +} diff --git a/cosmic-core/plugins/api/rate-limit/src/main/resources/META-INF/cloudstack/rate-limit/module.properties b/cosmic-core/plugins/api/rate-limit/src/main/resources/META-INF/cloudstack/rate-limit/module.properties new file mode 100644 index 0000000000..c998a87d93 --- /dev/null +++ b/cosmic-core/plugins/api/rate-limit/src/main/resources/META-INF/cloudstack/rate-limit/module.properties @@ -0,0 +1,18 @@ +# 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. +name=rate-limit +parent=api \ No newline at end of file diff --git a/cosmic-core/plugins/api/rate-limit/src/main/resources/META-INF/cloudstack/rate-limit/spring-rate-limit-context.xml b/cosmic-core/plugins/api/rate-limit/src/main/resources/META-INF/cloudstack/rate-limit/spring-rate-limit-context.xml new file mode 100644 index 0000000000..17153cf1c9 --- /dev/null +++ b/cosmic-core/plugins/api/rate-limit/src/main/resources/META-INF/cloudstack/rate-limit/spring-rate-limit-context.xml @@ -0,0 +1,32 @@ + + + + + + diff --git a/cosmic-core/plugins/api/rate-limit/src/test/java/org/apache/cloudstack/ratelimit/ApiRateLimitTest.java b/cosmic-core/plugins/api/rate-limit/src/test/java/org/apache/cloudstack/ratelimit/ApiRateLimitTest.java new file mode 100644 index 0000000000..1a311d6a40 --- /dev/null +++ b/cosmic-core/plugins/api/rate-limit/src/test/java/org/apache/cloudstack/ratelimit/ApiRateLimitTest.java @@ -0,0 +1,252 @@ +// 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 org.apache.cloudstack.ratelimit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import javax.naming.ConfigurationException; + +import com.cloud.configuration.Config; +import com.cloud.exception.RequestLimitException; +import com.cloud.user.Account; +import com.cloud.user.AccountService; +import com.cloud.user.AccountVO; +import com.cloud.user.User; +import com.cloud.user.UserVO; + +import org.apache.cloudstack.api.response.ApiLimitResponse; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +public class ApiRateLimitTest { + + static ApiRateLimitServiceImpl s_limitService = new ApiRateLimitServiceImpl(); + static AccountService s_accountService = mock(AccountService.class); + static ConfigurationDao s_configDao = mock(ConfigurationDao.class); + private static long s_acctIdSeq = 5L; + private static Account s_testAccount; + + @BeforeClass +public static void setUp() throws ConfigurationException { + + when(s_configDao.getValue(Config.ApiLimitInterval.key())).thenReturn(null); + when(s_configDao.getValue(Config.ApiLimitMax.key())).thenReturn(null); + when(s_configDao.getValue(Config.ApiLimitCacheSize.key())).thenReturn(null); + when(s_configDao.getValue(Config.ApiLimitEnabled.key())).thenReturn("true"); // enable api rate limiting + s_limitService._configDao = s_configDao; + + s_limitService.configure("ApiRateLimitTest", Collections. emptyMap()); + + s_limitService._accountService = s_accountService; + + // Standard responses + AccountVO acct = new AccountVO(s_acctIdSeq); + acct.setType(Account.ACCOUNT_TYPE_NORMAL); + acct.setAccountName("demo"); + s_testAccount = acct; + + when(s_accountService.getAccount(5L)).thenReturn(s_testAccount); + when(s_accountService.isRootAdmin(5L)).thenReturn(false); + } + + @Before + public void testSetUp() { + // reset counter for each test + s_limitService.resetApiLimit(null); + } + + private User createFakeUser() { + UserVO user = new UserVO(); + user.setAccountId(s_acctIdSeq); + return user; + } + + private boolean isUnderLimit(User key) { + try { + s_limitService.checkAccess(key, null); + return true; + } catch (RequestLimitException ex) { + return false; + } + } + + @Test + public void sequentialApiAccess() { + int allowedRequests = 1; + s_limitService.setMaxAllowed(allowedRequests); + s_limitService.setTimeToLive(1); + + User key = createFakeUser(); + assertTrue("Allow for the first request", isUnderLimit(key)); + + assertFalse("Second request should be blocked, since we assume that the two api " + " accesses take less than a second to perform", isUnderLimit(key)); + } + + @Test + public void canDoReasonableNumberOfApiAccessPerSecond() throws Exception { + int allowedRequests = 200; + s_limitService.setMaxAllowed(allowedRequests); + s_limitService.setTimeToLive(1); + + User key = createFakeUser(); + + for (int i = 0; i < allowedRequests; i++) { + assertTrue("We should allow " + allowedRequests + " requests per second, but failed at request " + i, isUnderLimit(key)); + } + + assertFalse("We should block >" + allowedRequests + " requests per second", isUnderLimit(key)); + } + + @Test + public void multipleClientsCanAccessWithoutBlocking() throws Exception { + int allowedRequests = 200; + s_limitService.setMaxAllowed(allowedRequests); + s_limitService.setTimeToLive(1); + + final User key = createFakeUser(); + + int clientCount = allowedRequests; + Runnable[] clients = new Runnable[clientCount]; + final boolean[] isUsable = new boolean[clientCount]; + + final CountDownLatch startGate = new CountDownLatch(1); + + final CountDownLatch endGate = new CountDownLatch(clientCount); + + for (int i = 0; i < isUsable.length; ++i) { + final int j = i; + clients[j] = new Runnable() { + + /** + * {@inheritDoc} + */ + @Override + public void run() { + try { + startGate.await(); + + isUsable[j] = isUnderLimit(key); + + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + endGate.countDown(); + } + } + }; + } + + ExecutorService executor = Executors.newFixedThreadPool(clientCount); + + for (Runnable runnable : clients) { + executor.execute(runnable); + } + + startGate.countDown(); + + endGate.await(); + + for (boolean b : isUsable) { + assertTrue("Concurrent client request should be allowed within limit", b); + } + } + + @Test + public void expiryOfCounterIsSupported() throws Exception { + int allowedRequests = 1; + s_limitService.setMaxAllowed(allowedRequests); + s_limitService.setTimeToLive(1); + + User key = createFakeUser(); + + assertTrue("The first request should be allowed", isUnderLimit(key)); + + // Allow the token to expire + Thread.sleep(1020); + + assertTrue("Another request after interval should be allowed as well", isUnderLimit(key)); + } + + @Test + public void verifyResetCounters() throws Exception { + int allowedRequests = 1; + s_limitService.setMaxAllowed(allowedRequests); + s_limitService.setTimeToLive(1); + + User key = createFakeUser(); + + assertTrue("The first request should be allowed", isUnderLimit(key)); + + assertFalse("Another request should be blocked", isUnderLimit(key)); + + s_limitService.resetApiLimit(key.getAccountId()); + + assertTrue("Another request should be allowed after reset counter", isUnderLimit(key)); + } + + @Test + public void verifySearchCounter() throws Exception { + int allowedRequests = 10; + s_limitService.setMaxAllowed(allowedRequests); + s_limitService.setTimeToLive(1); + + User key = createFakeUser(); + + for (int i = 0; i < 5; i++) { + assertTrue("Issued 5 requests", isUnderLimit(key)); + } + + ApiLimitResponse response = s_limitService.searchApiLimit(s_testAccount); + assertEquals("apiIssued is incorrect", 5, response.getApiIssued()); + assertEquals("apiAllowed is incorrect", 5, response.getApiAllowed()); + // using <= to account for inaccurate System.currentTimeMillis() clock in Windows environment + assertTrue("expiredAfter is incorrect", response.getExpireAfter() <= 1000); + + } + + @Test + public void disableApiLimit() throws Exception { + try { + int allowedRequests = 200; + s_limitService.setMaxAllowed(allowedRequests); + s_limitService.setTimeToLive(1); + s_limitService.setEnabled(false); + + User key = createFakeUser(); + + for (int i = 0; i < allowedRequests + 1; i++) { + assertTrue("We should allow more than " + allowedRequests + " requests per second when api throttling is disabled.", isUnderLimit(key)); + } + } finally { + s_limitService.setEnabled(true); // enable api throttling to avoid + // impacting other testcases + } + + } + +} diff --git a/cosmic-core/plugins/api/rate-limit/src/test/java/org/apache/cloudstack/ratelimit/integration/APITest.java b/cosmic-core/plugins/api/rate-limit/src/test/java/org/apache/cloudstack/ratelimit/integration/APITest.java new file mode 100644 index 0000000000..41f6cf9e4f --- /dev/null +++ b/cosmic-core/plugins/api/rate-limit/src/test/java/org/apache/cloudstack/ratelimit/integration/APITest.java @@ -0,0 +1,203 @@ +// 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 +// 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 org.apache.cloudstack.ratelimit.integration; + +import java.io.BufferedReader; +import java.io.EOFException; +import java.io.InputStreamReader; +import java.math.BigInteger; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Iterator; + +import com.cloud.api.ApiGsonHelper; +import com.cloud.utils.exception.CloudRuntimeException; +import com.google.gson.Gson; + +import org.apache.cloudstack.api.response.SuccessResponse; + +/** + * Base class for API Test + * + */ +public abstract class APITest { + + protected String rootUrl = "http://localhost:8080/client/api"; + protected String sessionKey = null; + protected String cookieToSent = null; + + /** + * Sending an api request through Http GET + * @param command command name + * @param params command query parameters in a HashMap + * @return http request response string + */ + protected String sendRequest(String command, HashMap params) { + try { + // Construct query string + StringBuilder sBuilder = new StringBuilder(); + sBuilder.append("command="); + sBuilder.append(command); + if (params != null && params.size() > 0) { + Iterator keys = params.keySet().iterator(); + while (keys.hasNext()) { + String key = keys.next(); + sBuilder.append("&"); + sBuilder.append(key); + sBuilder.append("="); + sBuilder.append(URLEncoder.encode(params.get(key), "UTF-8")); + } + } + + // Construct request url + String reqUrl = rootUrl + "?" + sBuilder.toString(); + + // Send Http GET request + URL url = new URL(reqUrl); + HttpURLConnection conn = (HttpURLConnection)url.openConnection(); + conn.setRequestMethod("GET"); + + if (!command.equals("login") && cookieToSent != null) { + // add the cookie to a request + conn.setRequestProperty("Cookie", cookieToSent); + } + conn.connect(); + + if (command.equals("login")) { + // if it is login call, store cookie + String headerName = null; + for (int i = 1; (headerName = conn.getHeaderFieldKey(i)) != null; i++) { + if (headerName.equals("Set-Cookie")) { + String cookie = conn.getHeaderField(i); + cookie = cookie.substring(0, cookie.indexOf(";")); + String cookieName = cookie.substring(0, cookie.indexOf("=")); + String cookieValue = cookie.substring(cookie.indexOf("=") + 1, cookie.length()); + cookieToSent = cookieName + "=" + cookieValue; + } + } + } + + // Get the response + StringBuilder response = new StringBuilder(); + BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream())); + String line; + try { + while ((line = rd.readLine()) != null) { + response.append(line); + } + } catch (EOFException ex) { + // ignore this exception + System.out.println("EOF exception due to java bug"); + } + rd.close(); + + return response.toString(); + + } catch (Exception e) { + throw new CloudRuntimeException("Problem with sending api request", e); + } + } + + protected String createMD5String(String password) { + 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); + return sb.toString(); + } + + protected Object fromSerializedString(String result, Class repCls) { + try { + if (result != null && !result.isEmpty()) { + // get real content + int start; + int end; + if (repCls == LoginResponse.class || repCls == SuccessResponse.class) { + + start = result.indexOf('{', result.indexOf('{') + 1); // find + // the + // second + // { + + end = result.lastIndexOf('}', result.lastIndexOf('}') - 1); // find + // the + // second + // } + // backwards + + } else { + // get real content + start = result.indexOf('{', result.indexOf('{', result.indexOf('{') + 1) + 1); // find + // the + // third + // { + end = result.lastIndexOf('}', result.lastIndexOf('}', result.lastIndexOf('}') - 1) - 1); // find + // the + // third + // } + // backwards + } + if (start < 0 || end < 0) { + throw new CloudRuntimeException("Response format is wrong: " + result); + } + String content = result.substring(start, end + 1); + Gson gson = ApiGsonHelper.getBuilder().create(); + return gson.fromJson(content, repCls); + } + return null; + } catch (RuntimeException e) { + throw new CloudRuntimeException("Caught runtime exception when doing GSON deserialization on: " + result, e); + } + } + + /** + * Login call + * @param username user name + * @param password password (plain password, we will do MD5 hash here for you) + * @return login response string + */ + protected void login(String username, String password) { + //String md5Psw = createMD5String(password); + // send login request + HashMap params = new HashMap(); + params.put("response", "json"); + params.put("username", username); + params.put("password", password); + String result = this.sendRequest("login", params); + LoginResponse loginResp = (LoginResponse)fromSerializedString(result, LoginResponse.class); + sessionKey = loginResp.getSessionkey(); + + } +} diff --git a/cosmic-core/plugins/api/rate-limit/src/test/java/org/apache/cloudstack/ratelimit/integration/LoginResponse.java b/cosmic-core/plugins/api/rate-limit/src/test/java/org/apache/cloudstack/ratelimit/integration/LoginResponse.java new file mode 100644 index 0000000000..0d574709e9 --- /dev/null +++ b/cosmic-core/plugins/api/rate-limit/src/test/java/org/apache/cloudstack/ratelimit/integration/LoginResponse.java @@ -0,0 +1,138 @@ +// 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 +// 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 org.apache.cloudstack.ratelimit.integration; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +import org.apache.cloudstack.api.BaseResponse; + +/** + * Login Response object + * + */ +public class LoginResponse extends BaseResponse { + + @SerializedName("timeout") + @Param(description = "session timeout period") + private String timeout; + + @SerializedName("sessionkey") + @Param(description = "login session key") + private String sessionkey; + + @SerializedName("username") + @Param(description = "login username") + private String username; + + @SerializedName("userid") + @Param(description = "login user internal uuid") + private String userid; + + @SerializedName("firstname") + @Param(description = "login user firstname") + private String firstname; + + @SerializedName("lastname") + @Param(description = "login user lastname") + private String lastname; + + @SerializedName("account") + @Param(description = "login user account type") + private String account; + + @SerializedName("domainid") + @Param(description = "login user domain id") + private String domainid; + + @SerializedName("type") + @Param(description = "login user type") + private int type; + + public String getTimeout() { + return timeout; + } + + public void setTimeout(String timeout) { + this.timeout = timeout; + } + + public String getSessionkey() { + return sessionkey; + } + + public void setSessionkey(String sessionkey) { + this.sessionkey = sessionkey; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getUserid() { + return userid; + } + + public void setUserid(String userid) { + this.userid = userid; + } + + public String getFirstname() { + return firstname; + } + + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + public String getLastname() { + return lastname; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } + + public String getAccount() { + return account; + } + + public void setAccount(String account) { + this.account = account; + } + + public String getDomainid() { + return domainid; + } + + public void setDomainid(String domainid) { + this.domainid = domainid; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + +} diff --git a/cosmic-core/plugins/api/rate-limit/src/test/java/org/apache/cloudstack/ratelimit/integration/RateLimitIntegrationTest.java b/cosmic-core/plugins/api/rate-limit/src/test/java/org/apache/cloudstack/ratelimit/integration/RateLimitIntegrationTest.java new file mode 100644 index 0000000000..0e2c7f8d6e --- /dev/null +++ b/cosmic-core/plugins/api/rate-limit/src/test/java/org/apache/cloudstack/ratelimit/integration/RateLimitIntegrationTest.java @@ -0,0 +1,207 @@ +// 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 +// 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 org.apache.cloudstack.ratelimit.integration; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.HashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import com.cloud.utils.exception.CloudRuntimeException; + +import org.apache.cloudstack.api.response.ApiLimitResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.junit.Before; +import org.junit.Test; + +/** + * Test fixture to do integration rate limit test. + * Currently we commented out this test suite since it requires a real MS and Db running. + */ +public class RateLimitIntegrationTest extends APITest { + + private static int apiMax = 25; // assuming ApiRateLimitService set api.throttling.max = 25 + + @Before + public void setup() { + // always reset count for each testcase + login("admin", "password"); + + // issue reset api limit calls + final HashMap params = new HashMap(); + params.put("response", "json"); + params.put("sessionkey", sessionKey); + String resetResult = sendRequest("resetApiLimit", params); + assertNotNull("Reset count failed!", fromSerializedString(resetResult, SuccessResponse.class)); + + } + + @Test + public void testNoApiLimitOnRootAdmin() throws Exception { + // issue list Accounts calls + final HashMap params = new HashMap(); + params.put("response", "json"); + params.put("listAll", "true"); + params.put("sessionkey", sessionKey); + // assuming ApiRateLimitService set api.throttling.max = 25 + int clientCount = 26; + Runnable[] clients = new Runnable[clientCount]; + final boolean[] isUsable = new boolean[clientCount]; + + final CountDownLatch startGate = new CountDownLatch(1); + + final CountDownLatch endGate = new CountDownLatch(clientCount); + + for (int i = 0; i < isUsable.length; ++i) { + final int j = i; + clients[j] = new Runnable() { + + /** + * {@inheritDoc} + */ + @Override + public void run() { + try { + startGate.await(); + + sendRequest("listAccounts", params); + + isUsable[j] = true; + + } catch (CloudRuntimeException e) { + isUsable[j] = false; + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + endGate.countDown(); + } + } + }; + } + + ExecutorService executor = Executors.newFixedThreadPool(clientCount); + + for (Runnable runnable : clients) { + executor.execute(runnable); + } + + startGate.countDown(); + + endGate.await(); + + int rejectCount = 0; + for (int i = 0; i < isUsable.length; ++i) { + if (!isUsable[i]) + rejectCount++; + } + + assertEquals("No request should be rejected!", 0, rejectCount); + + } + + @Test + public void testApiLimitOnUser() throws Exception { + // log in using normal user + login("demo", "password"); + // issue list Accounts calls + final HashMap params = new HashMap(); + params.put("response", "json"); + params.put("listAll", "true"); + params.put("sessionkey", sessionKey); + + int clientCount = apiMax + 1; + Runnable[] clients = new Runnable[clientCount]; + final boolean[] isUsable = new boolean[clientCount]; + + final CountDownLatch startGate = new CountDownLatch(1); + + final CountDownLatch endGate = new CountDownLatch(clientCount); + + for (int i = 0; i < isUsable.length; ++i) { + final int j = i; + clients[j] = new Runnable() { + + /** + * {@inheritDoc} + */ + @Override + public void run() { + try { + startGate.await(); + + sendRequest("listAccounts", params); + + isUsable[j] = true; + + } catch (CloudRuntimeException e) { + isUsable[j] = false; + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + endGate.countDown(); + } + } + }; + } + + ExecutorService executor = Executors.newFixedThreadPool(clientCount); + + for (Runnable runnable : clients) { + executor.execute(runnable); + } + + startGate.countDown(); + + endGate.await(); + + int rejectCount = 0; + for (int i = 0; i < isUsable.length; ++i) { + if (!isUsable[i]) + rejectCount++; + } + + assertEquals("Only one request should be rejected!", 1, rejectCount); + + } + + @Test + public void testGetApiLimitOnUser() throws Exception { + // log in using normal user + login("demo", "password"); + + // issue an api call + HashMap params = new HashMap(); + params.put("response", "json"); + params.put("listAll", "true"); + params.put("sessionkey", sessionKey); + sendRequest("listAccounts", params); + + // issue get api limit calls + final HashMap params2 = new HashMap(); + params2.put("response", "json"); + params2.put("sessionkey", sessionKey); + String getResult = sendRequest("getApiLimit", params2); + ApiLimitResponse getLimitResp = (ApiLimitResponse)fromSerializedString(getResult, ApiLimitResponse.class); + assertEquals("Issued api count is incorrect!", 2, getLimitResp.getApiIssued()); // should be 2 apis issues plus this getlimit api + assertEquals("Allowed api count is incorrect!", apiMax - 2, getLimitResp.getApiAllowed()); + } +} diff --git a/cosmic-core/plugins/dedicated-resources/pom.xml b/cosmic-core/plugins/dedicated-resources/pom.xml new file mode 100644 index 0000000000..2488b4a5c6 --- /dev/null +++ b/cosmic-core/plugins/dedicated-resources/pom.xml @@ -0,0 +1,11 @@ + + 4.0.0 + cloud-plugin-dedicated-resources + Cosmic Plugin - Dedicated Resources + + cloud.cosmic + cosmic-plugins + 5.1.0.0-SNAPSHOT + ../pom.xml + + \ No newline at end of file diff --git a/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicateClusterCmd.java b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicateClusterCmd.java new file mode 100644 index 0000000000..cbdc02635f --- /dev/null +++ b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicateClusterCmd.java @@ -0,0 +1,121 @@ +// 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 org.apache.cloudstack.api.commands; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import com.cloud.dc.DedicatedResources; +import com.cloud.event.EventTypes; +import com.cloud.user.Account; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.ClusterResponse; +import org.apache.cloudstack.api.response.DedicateClusterResponse; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.dedicated.DedicatedService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@APICommand(name = "dedicateCluster", description = "Dedicate an existing cluster", responseObject = DedicateClusterResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class DedicateClusterCmd extends BaseAsyncCmd { + public static final Logger s_logger = LoggerFactory.getLogger(DedicateClusterCmd.class.getName()); + + private static final String s_name = "dedicateclusterresponse"; + @Inject + DedicatedService dedicatedService; + + @Parameter(name = ApiConstants.CLUSTER_ID, type = CommandType.UUID, entityType = ClusterResponse.class, required = true, description = "the ID of the Cluster") + private Long clusterId; + + @Parameter(name = ApiConstants.DOMAIN_ID, + type = CommandType.UUID, + entityType = DomainResponse.class, + required = true, + description = "the ID of the containing domain") + private Long domainId; + + @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "the name of the account which needs dedication. Must be used with domainId.") + private String accountName; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getClusterId() { + return clusterId; + } + + public Long getDomainId() { + return domainId; + } + + public String getAccountName() { + return accountName; + } + + @Override + public String getEventType() { + return EventTypes.EVENT_DEDICATE_RESOURCE; + } + + @Override + public String getEventDescription() { + return "dedicating a cluster"; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute() { + List result = dedicatedService.dedicateCluster(getClusterId(), getDomainId(), getAccountName()); + ListResponse response = new ListResponse(); + List clusterResponseList = new ArrayList(); + if (result != null) { + for (DedicatedResources resource : result) { + DedicateClusterResponse clusterResponse = dedicatedService.createDedicateClusterResponse(resource); + clusterResponseList.add(clusterResponse); + } + response.setResponses(clusterResponseList); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to dedicate cluster"); + } + } + +} diff --git a/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicateHostCmd.java b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicateHostCmd.java new file mode 100644 index 0000000000..c2f6c0626b --- /dev/null +++ b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicateHostCmd.java @@ -0,0 +1,124 @@ +// 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 org.apache.cloudstack.api.commands; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import com.cloud.dc.DedicatedResources; +import com.cloud.event.EventTypes; +import com.cloud.user.Account; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DedicateHostResponse; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.HostResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.dedicated.DedicatedService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@APICommand(name = "dedicateHost", description = "Dedicates a host.", responseObject = DedicateHostResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class DedicateHostCmd extends BaseAsyncCmd { + public static final Logger s_logger = LoggerFactory.getLogger(DedicateHostCmd.class.getName()); + private static final String s_name = "dedicatehostresponse"; + @Inject + DedicatedService dedicatedService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, required = true, description = "the ID of the host to update") + private Long hostId; + + @Parameter(name = ApiConstants.DOMAIN_ID, + type = CommandType.UUID, + entityType = DomainResponse.class, + required = true, + description = "the ID of the containing domain") + private Long domainId; + + @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "the name of the account which needs dedication. Must be used with domainId.") + private String accountName; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getHostId() { + return hostId; + } + + public Long getDomainId() { + return domainId; + } + + public String getAccountName() { + return accountName; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute() { + List result = dedicatedService.dedicateHost(getHostId(), getDomainId(), getAccountName()); + ListResponse response = new ListResponse(); + List hostResponseList = new ArrayList(); + if (result != null) { + for (DedicatedResources resource : result) { + DedicateHostResponse hostResponse = dedicatedService.createDedicateHostResponse(resource); + hostResponseList.add(hostResponse); + } + response.setResponses(hostResponseList); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to dedicate host"); + } + } + + @Override + public String getEventType() { + return EventTypes.EVENT_DEDICATE_RESOURCE; + } + + @Override + public String getEventDescription() { + return "dedicating a host"; + } +} diff --git a/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicatePodCmd.java b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicatePodCmd.java new file mode 100644 index 0000000000..ebe8b35b19 --- /dev/null +++ b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicatePodCmd.java @@ -0,0 +1,125 @@ +// 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 org.apache.cloudstack.api.commands; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import com.cloud.dc.DedicatedResources; +import com.cloud.event.EventTypes; +import com.cloud.user.Account; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DedicatePodResponse; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.PodResponse; +import org.apache.cloudstack.dedicated.DedicatedService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@APICommand(name = "dedicatePod", description = "Dedicates a Pod.", responseObject = DedicatePodResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class DedicatePodCmd extends BaseAsyncCmd { + public static final Logger s_logger = LoggerFactory.getLogger(DedicatePodCmd.class.getName()); + + private static final String s_name = "dedicatepodresponse"; + @Inject + public DedicatedService dedicatedService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.POD_ID, type = CommandType.UUID, entityType = PodResponse.class, required = true, description = "the ID of the Pod") + private Long podId; + + @Parameter(name = ApiConstants.DOMAIN_ID, + type = CommandType.UUID, + entityType = DomainResponse.class, + required = true, + description = "the ID of the containing domain") + private Long domainId; + + @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "the name of the account which needs dedication. Must be used with domainId.") + private String accountName; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getPodId() { + return podId; + } + + public Long getDomainId() { + return domainId; + } + + public String getAccountName() { + return accountName; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute() { + List result = dedicatedService.dedicatePod(getPodId(), getDomainId(), getAccountName()); + ListResponse response = new ListResponse(); + List podResponseList = new ArrayList(); + if (result != null) { + for (DedicatedResources resource : result) { + DedicatePodResponse podresponse = dedicatedService.createDedicatePodResponse(resource); + podResponseList.add(podresponse); + } + response.setResponses(podResponseList); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to dedicate pod"); + } + } + + @Override + public String getEventType() { + return EventTypes.EVENT_DEDICATE_RESOURCE; + } + + @Override + public String getEventDescription() { + return "dedicating a pod"; + } +} diff --git a/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicateZoneCmd.java b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicateZoneCmd.java new file mode 100644 index 0000000000..06050c3f62 --- /dev/null +++ b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicateZoneCmd.java @@ -0,0 +1,125 @@ +// 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 org.apache.cloudstack.api.commands; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import com.cloud.dc.DedicatedResources; +import com.cloud.event.EventTypes; +import com.cloud.user.Account; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DedicateZoneResponse; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.dedicated.DedicatedService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@APICommand(name = "dedicateZone", description = "Dedicates a zones.", responseObject = DedicateZoneResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class DedicateZoneCmd extends BaseAsyncCmd { + public static final Logger s_logger = LoggerFactory.getLogger(DedicateZoneCmd.class.getName()); + + private static final String s_name = "dedicatezoneresponse"; + @Inject + public DedicatedService dedicatedService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, required = true, description = "the ID of the zone") + private Long zoneId; + + @Parameter(name = ApiConstants.DOMAIN_ID, + type = CommandType.UUID, + entityType = DomainResponse.class, + required = true, + description = "the ID of the containing domain") + private Long domainId; + + @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "the name of the account which needs dedication. Must be used with domainId.") + private String accountName; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getZoneId() { + return zoneId; + } + + public Long getDomainId() { + return domainId; + } + + public String getAccountName() { + return accountName; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute() { + List result = dedicatedService.dedicateZone(getZoneId(), getDomainId(), getAccountName()); + ListResponse response = new ListResponse(); + List zoneResponseList = new ArrayList(); + if (result != null) { + for (DedicatedResources resource : result) { + DedicateZoneResponse zoneresponse = dedicatedService.createDedicateZoneResponse(resource); + zoneResponseList.add(zoneresponse); + } + response.setResponses(zoneResponseList); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to dedicate zone"); + } + } + + @Override + public String getEventType() { + return EventTypes.EVENT_DEDICATE_RESOURCE; + } + + @Override + public String getEventDescription() { + return "dedicating a zone"; + } +} diff --git a/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedClustersCmd.java b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedClustersCmd.java new file mode 100644 index 0000000000..093bbb988a --- /dev/null +++ b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedClustersCmd.java @@ -0,0 +1,119 @@ +// 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 org.apache.cloudstack.api.commands; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import com.cloud.dc.DedicatedResourceVO; +import com.cloud.dc.DedicatedResources; +import com.cloud.utils.Pair; + +import org.apache.cloudstack.affinity.AffinityGroupResponse; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.ClusterResponse; +import org.apache.cloudstack.api.response.DedicateClusterResponse; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.dedicated.DedicatedService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@APICommand(name = "listDedicatedClusters", description = "Lists dedicated clusters.", responseObject = DedicateClusterResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class ListDedicatedClustersCmd extends BaseListCmd { + public static final Logger s_logger = LoggerFactory.getLogger(ListDedicatedClustersCmd.class.getName()); + + private static final String s_name = "listdedicatedclustersresponse"; + @Inject + DedicatedService dedicatedService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + @Parameter(name = ApiConstants.CLUSTER_ID, type = CommandType.UUID, entityType = ClusterResponse.class, description = "the ID of the cluster") + private Long clusterId; + + @Parameter(name = ApiConstants.DOMAIN_ID, + type = CommandType.UUID, + entityType = DomainResponse.class, + description = "the ID of the domain associated with the cluster") + private Long domainId; + + @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "the name of the account associated with the cluster. Must be used with domainId.") + private String accountName; + + @Parameter(name = ApiConstants.AFFINITY_GROUP_ID, + type = CommandType.UUID, + entityType = AffinityGroupResponse.class, + description = "list dedicated clusters by affinity group") + private Long affinityGroupId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getClusterId() { + return clusterId; + } + + public Long getDomainId() { + return domainId; + } + + public String getAccountName() { + return accountName; + } + + public Long getAffinityGroupId() { + return affinityGroupId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public void execute() { + Pair, Integer> result = dedicatedService.listDedicatedClusters(this); + ListResponse response = new ListResponse(); + List Responses = new ArrayList(); + if (result != null) { + for (DedicatedResources resource : result.first()) { + DedicateClusterResponse clusterResponse = dedicatedService.createDedicateClusterResponse(resource); + Responses.add(clusterResponse); + } + response.setResponses(Responses, result.second()); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to list dedicated clusters"); + } + } +} diff --git a/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedHostsCmd.java b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedHostsCmd.java new file mode 100644 index 0000000000..d3425132b4 --- /dev/null +++ b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedHostsCmd.java @@ -0,0 +1,116 @@ +// 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 org.apache.cloudstack.api.commands; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import com.cloud.dc.DedicatedResourceVO; +import com.cloud.dc.DedicatedResources; +import com.cloud.utils.Pair; + +import org.apache.cloudstack.affinity.AffinityGroupResponse; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DedicateHostResponse; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.HostResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.dedicated.DedicatedService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@APICommand(name = "listDedicatedHosts", description = "Lists dedicated hosts.", responseObject = DedicateHostResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class ListDedicatedHostsCmd extends BaseListCmd { + public static final Logger s_logger = LoggerFactory.getLogger(ListDedicatedHostsCmd.class.getName()); + + private static final String s_name = "listdedicatedhostsresponse"; + @Inject + DedicatedService dedicatedService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + @Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, description = "the ID of the host") + private Long hostId; + + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "the ID of the domain associated with the host") + private Long domainId; + + @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "the name of the account associated with the host. Must be used with domainId.") + private String accountName; + + @Parameter(name = ApiConstants.AFFINITY_GROUP_ID, + type = CommandType.UUID, + entityType = AffinityGroupResponse.class, + description = "list dedicated hosts by affinity group") + private Long affinityGroupId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getHostId() { + return hostId; + } + + public Long getDomainId() { + return domainId; + } + + public String getAccountName() { + return accountName; + } + + public Long getAffinityGroupId() { + return affinityGroupId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation///////////////////l + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public void execute() { + Pair, Integer> result = dedicatedService.listDedicatedHosts(this); + ListResponse response = new ListResponse(); + List Responses = new ArrayList(); + if (result != null) { + for (DedicatedResources resource : result.first()) { + DedicateHostResponse hostResponse = dedicatedService.createDedicateHostResponse(resource); + Responses.add(hostResponse); + } + response.setResponses(Responses, result.second()); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to list dedicated hosts"); + } + } +} diff --git a/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedPodsCmd.java b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedPodsCmd.java new file mode 100644 index 0000000000..37dc30b7a6 --- /dev/null +++ b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedPodsCmd.java @@ -0,0 +1,116 @@ +// 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 org.apache.cloudstack.api.commands; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import com.cloud.dc.DedicatedResourceVO; +import com.cloud.dc.DedicatedResources; +import com.cloud.utils.Pair; + +import org.apache.cloudstack.affinity.AffinityGroupResponse; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DedicatePodResponse; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.PodResponse; +import org.apache.cloudstack.dedicated.DedicatedService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@APICommand(name = "listDedicatedPods", description = "Lists dedicated pods.", responseObject = DedicatePodResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class ListDedicatedPodsCmd extends BaseListCmd { + public static final Logger s_logger = LoggerFactory.getLogger(ListDedicatedPodsCmd.class.getName()); + + private static final String s_name = "listdedicatedpodsresponse"; + @Inject + DedicatedService dedicatedService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + @Parameter(name = ApiConstants.POD_ID, type = CommandType.UUID, entityType = PodResponse.class, description = "the ID of the pod") + private Long podId; + + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "the ID of the domain associated with the pod") + private Long domainId; + + @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "the name of the account associated with the pod. Must be used with domainId.") + private String accountName; + + @Parameter(name = ApiConstants.AFFINITY_GROUP_ID, + type = CommandType.UUID, + entityType = AffinityGroupResponse.class, + description = "list dedicated pods by affinity group") + private Long affinityGroupId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getPodId() { + return podId; + } + + public Long getDomainId() { + return domainId; + } + + public String getAccountName() { + return accountName; + } + + public Long getAffinityGroupId() { + return affinityGroupId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public void execute() { + Pair, Integer> result = dedicatedService.listDedicatedPods(this); + ListResponse response = new ListResponse(); + List Responses = new ArrayList(); + if (result != null) { + for (DedicatedResources resource : result.first()) { + DedicatePodResponse podresponse = dedicatedService.createDedicatePodResponse(resource); + Responses.add(podresponse); + } + response.setResponses(Responses, result.second()); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to list dedicated pods"); + } + } +} diff --git a/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedZonesCmd.java b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedZonesCmd.java new file mode 100644 index 0000000000..b931998c28 --- /dev/null +++ b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedZonesCmd.java @@ -0,0 +1,116 @@ +// 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 org.apache.cloudstack.api.commands; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import com.cloud.dc.DedicatedResourceVO; +import com.cloud.dc.DedicatedResources; +import com.cloud.utils.Pair; + +import org.apache.cloudstack.affinity.AffinityGroupResponse; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DedicateZoneResponse; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.dedicated.DedicatedService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@APICommand(name = "listDedicatedZones", description = "List dedicated zones.", responseObject = DedicateZoneResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class ListDedicatedZonesCmd extends BaseListCmd { + public static final Logger s_logger = LoggerFactory.getLogger(ListDedicatedZonesCmd.class.getName()); + + private static final String s_name = "listdedicatedzonesresponse"; + @Inject + DedicatedService _dedicatedservice; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + @Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, description = "the ID of the Zone") + private Long zoneId; + + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "the ID of the domain associated with the zone") + private Long domainId; + + @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "the name of the account associated with the zone. Must be used with domainId.") + private String accountName; + + @Parameter(name = ApiConstants.AFFINITY_GROUP_ID, + type = CommandType.UUID, + entityType = AffinityGroupResponse.class, + description = "list dedicated zones by affinity group") + private Long affinityGroupId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getZoneId() { + return zoneId; + } + + public Long getDomainId() { + return domainId; + } + + public String getAccountName() { + return accountName; + } + + public Long getAffinityGroupId() { + return affinityGroupId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public void execute() { + Pair, Integer> result = _dedicatedservice.listDedicatedZones(this); + ListResponse response = new ListResponse(); + List Responses = new ArrayList(); + if (result != null) { + for (DedicatedResources resource : result.first()) { + DedicateZoneResponse zoneResponse = _dedicatedservice.createDedicateZoneResponse(resource); + Responses.add(zoneResponse); + } + response.setResponses(Responses, result.second()); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to list dedicated zones"); + } + } +} diff --git a/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedClusterCmd.java b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedClusterCmd.java new file mode 100644 index 0000000000..8e73187ecc --- /dev/null +++ b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedClusterCmd.java @@ -0,0 +1,94 @@ +// 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 org.apache.cloudstack.api.commands; + +import javax.inject.Inject; + +import com.cloud.event.EventTypes; +import com.cloud.user.Account; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.ClusterResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.dedicated.DedicatedService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@APICommand(name = "releaseDedicatedCluster", description = "Release the dedication for cluster", responseObject = SuccessResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class ReleaseDedicatedClusterCmd extends BaseAsyncCmd { + public static final Logger s_logger = LoggerFactory.getLogger(ReleaseDedicatedClusterCmd.class.getName()); + + private static final String s_name = "releasededicatedclusterresponse"; + @Inject + DedicatedService dedicatedService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.CLUSTER_ID, type = CommandType.UUID, entityType = ClusterResponse.class, required = true, description = "the ID of the Cluster") + private Long clusterId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getClusterId() { + return clusterId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute() { + boolean result = dedicatedService.releaseDedicatedResource(null, null, getClusterId(), null); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to release dedicated cluster"); + } + } + + @Override + public String getEventType() { + return EventTypes.EVENT_DEDICATE_RESOURCE_RELEASE; + } + + @Override + public String getEventDescription() { + return "releasing dedicated cluster"; + } +} diff --git a/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedHostCmd.java b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedHostCmd.java new file mode 100644 index 0000000000..d129786a6d --- /dev/null +++ b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedHostCmd.java @@ -0,0 +1,94 @@ +// 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 org.apache.cloudstack.api.commands; + +import javax.inject.Inject; + +import com.cloud.event.EventTypes; +import com.cloud.user.Account; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.HostResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.dedicated.DedicatedService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@APICommand(name = "releaseDedicatedHost", description = "Release the dedication for host", responseObject = SuccessResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class ReleaseDedicatedHostCmd extends BaseAsyncCmd { + public static final Logger s_logger = LoggerFactory.getLogger(ReleaseDedicatedHostCmd.class.getName()); + + private static final String s_name = "releasededicatedhostresponse"; + @Inject + DedicatedService dedicatedService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, required = true, description = "the ID of the host") + private Long hostId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getHostId() { + return hostId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute() { + boolean result = dedicatedService.releaseDedicatedResource(null, null, null, getHostId()); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to release dedicated Host"); + } + } + + @Override + public String getEventType() { + return EventTypes.EVENT_DEDICATE_RESOURCE_RELEASE; + } + + @Override + public String getEventDescription() { + return "releasing dedicated host"; + } +} diff --git a/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedPodCmd.java b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedPodCmd.java new file mode 100644 index 0000000000..70d67190c7 --- /dev/null +++ b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedPodCmd.java @@ -0,0 +1,94 @@ +// 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 org.apache.cloudstack.api.commands; + +import javax.inject.Inject; + +import com.cloud.event.EventTypes; +import com.cloud.user.Account; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.PodResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.dedicated.DedicatedService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@APICommand(name = "releaseDedicatedPod", description = "Release the dedication for the pod", responseObject = SuccessResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class ReleaseDedicatedPodCmd extends BaseAsyncCmd { + public static final Logger s_logger = LoggerFactory.getLogger(ReleaseDedicatedPodCmd.class.getName()); + + private static final String s_name = "releasededicatedpodresponse"; + @Inject + DedicatedService dedicatedService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.POD_ID, type = CommandType.UUID, entityType = PodResponse.class, required = true, description = "the ID of the Pod") + private Long podId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getPodId() { + return podId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute() { + boolean result = dedicatedService.releaseDedicatedResource(null, getPodId(), null, null); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to release dedicated pod"); + } + } + + @Override + public String getEventType() { + return EventTypes.EVENT_DEDICATE_RESOURCE_RELEASE; + } + + @Override + public String getEventDescription() { + return "releasing dedicated pod"; + } +} diff --git a/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedZoneCmd.java b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedZoneCmd.java new file mode 100644 index 0000000000..374becb296 --- /dev/null +++ b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedZoneCmd.java @@ -0,0 +1,94 @@ +// 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 org.apache.cloudstack.api.commands; + +import javax.inject.Inject; + +import com.cloud.event.EventTypes; +import com.cloud.user.Account; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.dedicated.DedicatedService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@APICommand(name = "releaseDedicatedZone", description = "Release dedication of zone", responseObject = SuccessResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class ReleaseDedicatedZoneCmd extends BaseAsyncCmd { + public static final Logger s_logger = LoggerFactory.getLogger(ReleaseDedicatedZoneCmd.class.getName()); + + private static final String s_name = "releasededicatedzoneresponse"; + @Inject + DedicatedService dedicatedService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, required = true, description = "the ID of the Zone") + private Long zoneId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getZoneId() { + return zoneId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute() { + boolean result = dedicatedService.releaseDedicatedResource(getZoneId(), null, null, null); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to release dedicated zone"); + } + } + + @Override + public String getEventType() { + return EventTypes.EVENT_DEDICATE_RESOURCE_RELEASE; + } + + @Override + public String getEventDescription() { + return "releasing dedicated zone"; + } +} diff --git a/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/response/DedicateClusterResponse.java b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/response/DedicateClusterResponse.java new file mode 100644 index 0000000000..3b6ec1d9b9 --- /dev/null +++ b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/response/DedicateClusterResponse.java @@ -0,0 +1,96 @@ +// 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 org.apache.cloudstack.api.response; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +import org.apache.cloudstack.api.BaseResponse; + +public class DedicateClusterResponse extends BaseResponse { + @SerializedName("id") + @Param(description = "the ID of the dedicated resource") + private String id; + + @SerializedName("clusterid") + @Param(description = "the ID of the cluster") + private String clusterId; + + @SerializedName("clustername") + @Param(description = "the name of the cluster") + private String clusterName; + + @SerializedName("domainid") + @Param(description = "the domain ID of the cluster") + private String domainId; + + @SerializedName("accountid") + @Param(description = "the Account ID of the cluster") + private String accountId; + + @SerializedName("affinitygroupid") + @Param(description = "the Dedication Affinity Group ID of the cluster") + private String affinityGroupId; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getClusterId() { + return clusterId; + } + + public void setClusterId(String clusterId) { + this.clusterId = clusterId; + } + + public String getClusterName() { + return clusterName; + } + + public void setClusterName(String clusterName) { + this.clusterName = clusterName; + } + + public String getDomainId() { + return domainId; + } + + public void setDomainId(String domainId) { + this.domainId = domainId; + } + + public String getAccountId() { + return accountId; + } + + public void setAccountId(String accountId) { + this.accountId = accountId; + } + + public String getAffinityGroupId() { + return affinityGroupId; + } + + public void setAffinityGroupId(String affinityGroupId) { + this.affinityGroupId = affinityGroupId; + } +} diff --git a/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/response/DedicateHostResponse.java b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/response/DedicateHostResponse.java new file mode 100644 index 0000000000..f397a55c0d --- /dev/null +++ b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/response/DedicateHostResponse.java @@ -0,0 +1,96 @@ +// 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 org.apache.cloudstack.api.response; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +import org.apache.cloudstack.api.BaseResponse; + +public class DedicateHostResponse extends BaseResponse { + @SerializedName("id") + @Param(description = "the ID of the dedicated resource") + private String id; + + @SerializedName("hostid") + @Param(description = "the ID of the host") + private String hostId; + + @SerializedName("hostname") + @Param(description = "the name of the host") + private String hostName; + + @SerializedName("domainid") + @Param(description = "the domain ID of the host") + private String domainId; + + @SerializedName("accountid") + @Param(description = "the Account ID of the host") + private String accountId; + + @SerializedName("affinitygroupid") + @Param(description = "the Dedication Affinity Group ID of the host") + private String affinityGroupId; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getHostId() { + return hostId; + } + + public void setHostId(String hostId) { + this.hostId = hostId; + } + + public String getHostName() { + return hostName; + } + + public void setHostName(String hostName) { + this.hostName = hostName; + } + + public String getDomainId() { + return domainId; + } + + public void setDomainId(String domainId) { + this.domainId = domainId; + } + + public String getAccountId() { + return accountId; + } + + public void setAccountId(String accountId) { + this.accountId = accountId; + } + + public String getAffinityGroupId() { + return affinityGroupId; + } + + public void setAffinityGroupId(String affinityGroupId) { + this.affinityGroupId = affinityGroupId; + } +} diff --git a/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/response/DedicatePodResponse.java b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/response/DedicatePodResponse.java new file mode 100644 index 0000000000..a066e6d34b --- /dev/null +++ b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/response/DedicatePodResponse.java @@ -0,0 +1,99 @@ +// 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 org.apache.cloudstack.api.response; + +import com.cloud.dc.DedicatedResources; +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; + +@EntityReference(value = DedicatedResources.class) +public class DedicatePodResponse extends BaseResponse { + @SerializedName("id") + @Param(description = "the ID of the dedicated resource") + private String id; + + @SerializedName("podid") + @Param(description = "the ID of the Pod") + private String podId; + + @SerializedName("podname") + @Param(description = "the Name of the Pod") + private String podName; + + @SerializedName("domainid") + @Param(description = "the domain ID to which the Pod is dedicated") + private String domainId; + + @SerializedName("accountid") + @Param(description = "the Account Id to which the Pod is dedicated") + private String accountId; + + @SerializedName("affinitygroupid") + @Param(description = "the Dedication Affinity Group ID of the pod") + private String affinityGroupId; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getPodId() { + return podId; + } + + public void setPodId(String podId) { + this.podId = podId; + } + + public String getPodName() { + return podName; + } + + public void setPodName(String podName) { + this.podName = podName; + } + + public String getDomainId() { + return domainId; + } + + public void setDomainId(String domainId) { + this.domainId = domainId; + } + + public String getAccountId() { + return accountId; + } + + public void setAccountId(String accountId) { + this.accountId = accountId; + } + + public String getAffinityGroupId() { + return affinityGroupId; + } + + public void setAffinityGroupId(String affinityGroupId) { + this.affinityGroupId = affinityGroupId; + } +} diff --git a/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/response/DedicateZoneResponse.java b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/response/DedicateZoneResponse.java new file mode 100644 index 0000000000..564ef3fde9 --- /dev/null +++ b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/response/DedicateZoneResponse.java @@ -0,0 +1,99 @@ +// 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 org.apache.cloudstack.api.response; + +import com.cloud.dc.DedicatedResources; +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; + +@EntityReference(value = DedicatedResources.class) +public class DedicateZoneResponse extends BaseResponse { + @SerializedName("id") + @Param(description = "the ID of the dedicated resource") + private String id; + + @SerializedName("zoneid") + @Param(description = "the ID of the Zone") + private String zoneId; + + @SerializedName("zonename") + @Param(description = "the Name of the Zone") + private String zoneName; + + @SerializedName("domainid") + @Param(description = "the domain ID to which the Zone is dedicated") + private String domainId; + + @SerializedName("accountid") + @Param(description = "the Account Id to which the Zone is dedicated") + private String accountId; + + @SerializedName("affinitygroupid") + @Param(description = "the Dedication Affinity Group ID of the zone") + private String affinityGroupId; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getZoneId() { + return zoneId; + } + + public void setZoneId(String zoneId) { + this.zoneId = zoneId; + } + + public String getZoneName() { + return zoneName; + } + + public void setZoneName(String zoneName) { + this.zoneName = zoneName; + } + + public String getDomainId() { + return domainId; + } + + public void setDomainId(String domainId) { + this.domainId = domainId; + } + + public String getAccountId() { + return accountId; + } + + public void setAccountId(String accountId) { + this.accountId = accountId; + } + + public String getAffinityGroupId() { + return affinityGroupId; + } + + public void setAffinityGroupId(String affinityGroupId) { + this.affinityGroupId = affinityGroupId; + } +} diff --git a/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/dedicated/DedicatedResourceManagerImpl.java b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/dedicated/DedicatedResourceManagerImpl.java new file mode 100644 index 0000000000..f005fc4b66 --- /dev/null +++ b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/dedicated/DedicatedResourceManagerImpl.java @@ -0,0 +1,957 @@ +// 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 org.apache.cloudstack.dedicated; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.ejb.Local; +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import com.cloud.configuration.Config; +import com.cloud.dc.ClusterVO; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.DedicatedResourceVO; +import com.cloud.dc.DedicatedResources; +import com.cloud.dc.HostPodVO; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.dc.dao.DedicatedResourceDao; +import com.cloud.dc.dao.HostPodDao; +import com.cloud.domain.DomainVO; +import com.cloud.domain.dao.DomainDao; +import com.cloud.event.ActionEvent; +import com.cloud.event.EventTypes; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.AccountVO; +import com.cloud.user.dao.AccountDao; +import com.cloud.utils.DateUtil; +import com.cloud.utils.NumbersUtil; +import com.cloud.utils.Pair; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallback; +import com.cloud.utils.db.TransactionCallbackNoReturn; +import com.cloud.utils.db.TransactionStatus; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.dao.UserVmDao; + +import org.apache.cloudstack.affinity.AffinityGroup; +import org.apache.cloudstack.affinity.AffinityGroupService; +import org.apache.cloudstack.affinity.dao.AffinityGroupDao; +import org.apache.cloudstack.api.commands.DedicateClusterCmd; +import org.apache.cloudstack.api.commands.DedicateHostCmd; +import org.apache.cloudstack.api.commands.DedicatePodCmd; +import org.apache.cloudstack.api.commands.DedicateZoneCmd; +import org.apache.cloudstack.api.commands.ListDedicatedClustersCmd; +import org.apache.cloudstack.api.commands.ListDedicatedHostsCmd; +import org.apache.cloudstack.api.commands.ListDedicatedPodsCmd; +import org.apache.cloudstack.api.commands.ListDedicatedZonesCmd; +import org.apache.cloudstack.api.commands.ReleaseDedicatedClusterCmd; +import org.apache.cloudstack.api.commands.ReleaseDedicatedHostCmd; +import org.apache.cloudstack.api.commands.ReleaseDedicatedPodCmd; +import org.apache.cloudstack.api.commands.ReleaseDedicatedZoneCmd; +import org.apache.cloudstack.api.response.DedicateClusterResponse; +import org.apache.cloudstack.api.response.DedicateHostResponse; +import org.apache.cloudstack.api.response.DedicatePodResponse; +import org.apache.cloudstack.api.response.DedicateZoneResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +@Component +@Local({DedicatedService.class}) +public class DedicatedResourceManagerImpl implements DedicatedService { + private static final Logger s_logger = LoggerFactory.getLogger(DedicatedResourceManagerImpl.class); + + @Inject + AccountDao _accountDao; + @Inject + DomainDao _domainDao; + @Inject + HostPodDao _podDao; + @Inject + ClusterDao _clusterDao; + @Inject + HostDao _hostDao; + @Inject + DedicatedResourceDao _dedicatedDao; + @Inject + DataCenterDao _zoneDao; + @Inject + AccountManager _accountMgr; + @Inject + UserVmDao _userVmDao; + @Inject + ConfigurationDao _configDao; + @Inject + AffinityGroupDao _affinityGroupDao; + + @Inject + AffinityGroupService _affinityGroupService; + + private int capacityReleaseInterval; + + public boolean configure(final String name, final Map params) throws ConfigurationException { + capacityReleaseInterval = NumbersUtil.parseInt(_configDao.getValue(Config.CapacitySkipcountingHours.key()), 3600); + return true; + } + + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_DEDICATE_RESOURCE, eventDescription = "dedicating a Zone") + public List dedicateZone(final Long zoneId, final Long domainId, final String accountName) { + Long accountId = null; + List hosts = null; + if (accountName != null) { + Account caller = CallContext.current().getCallingAccount(); + Account owner = _accountMgr.finalizeOwner(caller, accountName, domainId, null); + accountId = owner.getId(); + } + List childDomainIds = getDomainChildIds(domainId); + childDomainIds.add(domainId); + checkAccountAndDomain(accountId, domainId); + final DataCenterVO dc = _zoneDao.findById(zoneId); + if (dc == null) { + throw new InvalidParameterValueException("Unable to find zone by id " + zoneId); + } else { + DedicatedResourceVO dedicatedZone = _dedicatedDao.findByZoneId(zoneId); + //check if zone is dedicated + if (dedicatedZone != null) { + s_logger.error("Zone " + dc.getName() + " is already dedicated"); + throw new CloudRuntimeException("Zone " + dc.getName() + " is already dedicated"); + } + + //check if any resource under this zone is dedicated to different account or sub-domain + List pods = _podDao.listByDataCenterId(dc.getId()); + List podsToRelease = new ArrayList(); + List clustersToRelease = new ArrayList(); + List hostsToRelease = new ArrayList(); + for (HostPodVO pod : pods) { + DedicatedResourceVO dPod = _dedicatedDao.findByPodId(pod.getId()); + if (dPod != null) { + if (!(childDomainIds.contains(dPod.getDomainId()))) { + throw new CloudRuntimeException("Pod " + pod.getName() + " under this Zone " + dc.getName() + " is dedicated to different account/domain"); + } + if (accountId != null) { + if (dPod.getAccountId().equals(accountId)) { + podsToRelease.add(dPod); + } else { + s_logger.error("Pod " + pod.getName() + " under this Zone " + dc.getName() + " is dedicated to different account/domain"); + throw new CloudRuntimeException("Pod " + pod.getName() + " under this Zone " + dc.getName() + " is dedicated to different account/domain"); + } + } else { + if (dPod.getAccountId() == null && dPod.getDomainId().equals(domainId)) { + podsToRelease.add(dPod); + } + } + } + } + + for (DedicatedResourceVO dr : podsToRelease) { + releaseDedicatedResource(null, dr.getPodId(), null, null); + } + + List clusters = _clusterDao.listClustersByDcId(dc.getId()); + for (ClusterVO cluster : clusters) { + DedicatedResourceVO dCluster = _dedicatedDao.findByClusterId(cluster.getId()); + if (dCluster != null) { + if (!(childDomainIds.contains(dCluster.getDomainId()))) { + throw new CloudRuntimeException("Cluster " + cluster.getName() + " under this Zone " + dc.getName() + " is dedicated to different account/domain"); + } + if (accountId != null) { + if (dCluster.getAccountId().equals(accountId)) { + clustersToRelease.add(dCluster); + } else { + s_logger.error("Cluster " + cluster.getName() + " under this Zone " + dc.getName() + " is dedicated to different account/domain"); + throw new CloudRuntimeException("Cluster " + cluster.getName() + " under this Zone " + dc.getName() + + " is dedicated to different account/domain"); + } + } else { + if (dCluster.getAccountId() == null && dCluster.getDomainId().equals(domainId)) { + clustersToRelease.add(dCluster); + } + } + } + } + + for (DedicatedResourceVO dr : clustersToRelease) { + releaseDedicatedResource(null, null, dr.getClusterId(), null); + } + + hosts = _hostDao.listByDataCenterId(dc.getId()); + for (HostVO host : hosts) { + DedicatedResourceVO dHost = _dedicatedDao.findByHostId(host.getId()); + if (dHost != null) { + if (!(childDomainIds.contains(dHost.getDomainId()))) { + throw new CloudRuntimeException("Host " + host.getName() + " under this Zone " + dc.getName() + " is dedicated to different account/domain"); + } + if (accountId != null) { + if (dHost.getAccountId().equals(accountId)) { + hostsToRelease.add(dHost); + } else { + s_logger.error("Host " + host.getName() + " under this Zone " + dc.getName() + " is dedicated to different account/domain"); + throw new CloudRuntimeException("Host " + host.getName() + " under this Zone " + dc.getName() + " is dedicated to different account/domain"); + } + } else { + if (dHost.getAccountId() == null && dHost.getDomainId().equals(domainId)) { + hostsToRelease.add(dHost); + } + } + } + } + + for (DedicatedResourceVO dr : hostsToRelease) { + releaseDedicatedResource(null, null, null, dr.getHostId()); + } + } + + checkHostsSuitabilityForExplicitDedication(accountId, childDomainIds, hosts); + + final Long accountIdFinal = accountId; + return Transaction.execute(new TransactionCallback>() { + @Override + public List doInTransaction(TransactionStatus status) { + // find or create the affinity group by name under this account/domain + AffinityGroup group = findOrCreateDedicatedAffinityGroup(domainId, accountIdFinal); + if (group == null) { + s_logger.error("Unable to dedicate zone due to, failed to create dedication affinity group"); + throw new CloudRuntimeException("Failed to dedicate zone. Please contact Cloud Support."); + } + + DedicatedResourceVO dedicatedResource = new DedicatedResourceVO(zoneId, null, null, null, null, null, group.getId()); + try { + dedicatedResource.setDomainId(domainId); + if (accountIdFinal != null) { + dedicatedResource.setAccountId(accountIdFinal); + } + dedicatedResource = _dedicatedDao.persist(dedicatedResource); + + // save the domainId in the zone + dc.setDomainId(domainId); + if (!_zoneDao.update(zoneId, dc)) { + throw new CloudRuntimeException("Failed to dedicate zone, could not set domainId. Please contact Cloud Support."); + } + + } catch (Exception e) { + s_logger.error("Unable to dedicate zone due to " + e.getMessage(), e); + throw new CloudRuntimeException("Failed to dedicate zone. Please contact Cloud Support."); + } + + List result = new ArrayList(); + result.add(dedicatedResource); + return result; + + } + }); + } + + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_DEDICATE_RESOURCE, eventDescription = "dedicating a Pod") + public List dedicatePod(final Long podId, final Long domainId, final String accountName) { + Long accountId = null; + if (accountName != null) { + Account caller = CallContext.current().getCallingAccount(); + Account owner = _accountMgr.finalizeOwner(caller, accountName, domainId, null); + accountId = owner.getId(); + } + List childDomainIds = getDomainChildIds(domainId); + childDomainIds.add(domainId); + checkAccountAndDomain(accountId, domainId); + HostPodVO pod = _podDao.findById(podId); + List hosts = null; + if (pod == null) { + throw new InvalidParameterValueException("Unable to find pod by id " + podId); + } else { + DedicatedResourceVO dedicatedPod = _dedicatedDao.findByPodId(podId); + DedicatedResourceVO dedicatedZoneOfPod = _dedicatedDao.findByZoneId(pod.getDataCenterId()); + //check if pod is dedicated + if (dedicatedPod != null) { + s_logger.error("Pod " + pod.getName() + " is already dedicated"); + throw new CloudRuntimeException("Pod " + pod.getName() + " is already dedicated"); + } + + if (dedicatedZoneOfPod != null) { + boolean domainIdInChildreanList = getDomainChildIds(dedicatedZoneOfPod.getDomainId()).contains(domainId); + //can dedicate a pod to an account/domain if zone is dedicated to parent-domain + if (dedicatedZoneOfPod.getAccountId() != null || (accountId == null && !domainIdInChildreanList) || + (accountId != null && !(dedicatedZoneOfPod.getDomainId().equals(domainId) || domainIdInChildreanList))) { + DataCenterVO zone = _zoneDao.findById(pod.getDataCenterId()); + s_logger.error("Cannot dedicate Pod. Its zone is already dedicated"); + throw new CloudRuntimeException("Pod's Zone " + zone.getName() + " is already dedicated"); + } + } + + //check if any resource under this pod is dedicated to different account or sub-domain + List clusters = _clusterDao.listByPodId(pod.getId()); + List clustersToRelease = new ArrayList(); + List hostsToRelease = new ArrayList(); + for (ClusterVO cluster : clusters) { + DedicatedResourceVO dCluster = _dedicatedDao.findByClusterId(cluster.getId()); + if (dCluster != null) { + if (!(childDomainIds.contains(dCluster.getDomainId()))) { + throw new CloudRuntimeException("Cluster " + cluster.getName() + " under this Pod " + pod.getName() + " is dedicated to different account/domain"); + } + /*if all dedicated resources belongs to same account and domain then we should release dedication + and make new entry for this Pod*/ + if (accountId != null) { + if (dCluster.getAccountId().equals(accountId)) { + clustersToRelease.add(dCluster); + } else { + s_logger.error("Cluster " + cluster.getName() + " under this Pod " + pod.getName() + " is dedicated to different account/domain"); + throw new CloudRuntimeException("Cluster " + cluster.getName() + " under this Pod " + pod.getName() + + " is dedicated to different account/domain"); + } + } else { + if (dCluster.getAccountId() == null && dCluster.getDomainId().equals(domainId)) { + clustersToRelease.add(dCluster); + } + } + } + } + + for (DedicatedResourceVO dr : clustersToRelease) { + releaseDedicatedResource(null, null, dr.getClusterId(), null); + } + + hosts = _hostDao.findByPodId(pod.getId()); + for (HostVO host : hosts) { + DedicatedResourceVO dHost = _dedicatedDao.findByHostId(host.getId()); + if (dHost != null) { + if (!(getDomainChildIds(domainId).contains(dHost.getDomainId()))) { + throw new CloudRuntimeException("Host " + host.getName() + " under this Pod " + pod.getName() + " is dedicated to different account/domain"); + } + if (accountId != null) { + if (dHost.getAccountId().equals(accountId)) { + hostsToRelease.add(dHost); + } else { + s_logger.error("Host " + host.getName() + " under this Pod " + pod.getName() + " is dedicated to different account/domain"); + throw new CloudRuntimeException("Host " + host.getName() + " under this Pod " + pod.getName() + " is dedicated to different account/domain"); + } + } else { + if (dHost.getAccountId() == null && dHost.getDomainId().equals(domainId)) { + hostsToRelease.add(dHost); + } + } + } + } + + for (DedicatedResourceVO dr : hostsToRelease) { + releaseDedicatedResource(null, null, null, dr.getHostId()); + } + } + + checkHostsSuitabilityForExplicitDedication(accountId, childDomainIds, hosts); + + final Long accountIdFinal = accountId; + return Transaction.execute(new TransactionCallback>() { + @Override + public List doInTransaction(TransactionStatus status) { + // find or create the affinity group by name under this account/domain + AffinityGroup group = findOrCreateDedicatedAffinityGroup(domainId, accountIdFinal); + if (group == null) { + s_logger.error("Unable to dedicate zone due to, failed to create dedication affinity group"); + throw new CloudRuntimeException("Failed to dedicate zone. Please contact Cloud Support."); + } + DedicatedResourceVO dedicatedResource = new DedicatedResourceVO(null, podId, null, null, null, null, group.getId()); + try { + dedicatedResource.setDomainId(domainId); + if (accountIdFinal != null) { + dedicatedResource.setAccountId(accountIdFinal); + } + dedicatedResource = _dedicatedDao.persist(dedicatedResource); + } catch (Exception e) { + s_logger.error("Unable to dedicate pod due to " + e.getMessage(), e); + throw new CloudRuntimeException("Failed to dedicate pod. Please contact Cloud Support."); + } + + List result = new ArrayList(); + result.add(dedicatedResource); + return result; + } + }); + } + + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_DEDICATE_RESOURCE, eventDescription = "dedicating a Cluster") + public List dedicateCluster(final Long clusterId, final Long domainId, final String accountName) { + Long accountId = null; + List hosts = null; + if (accountName != null) { + Account caller = CallContext.current().getCallingAccount(); + Account owner = _accountMgr.finalizeOwner(caller, accountName, domainId, null); + accountId = owner.getId(); + } + List childDomainIds = getDomainChildIds(domainId); + childDomainIds.add(domainId); + checkAccountAndDomain(accountId, domainId); + ClusterVO cluster = _clusterDao.findById(clusterId); + if (cluster == null) { + throw new InvalidParameterValueException("Unable to find cluster by id " + clusterId); + } else { + DedicatedResourceVO dedicatedCluster = _dedicatedDao.findByClusterId(clusterId); + DedicatedResourceVO dedicatedPodOfCluster = _dedicatedDao.findByPodId(cluster.getPodId()); + DedicatedResourceVO dedicatedZoneOfCluster = _dedicatedDao.findByZoneId(cluster.getDataCenterId()); + + //check if cluster is dedicated + if (dedicatedCluster != null) { + s_logger.error("Cluster " + cluster.getName() + " is already dedicated"); + throw new CloudRuntimeException("Cluster " + cluster.getName() + " is already dedicated"); + } + + if (dedicatedPodOfCluster != null) { + boolean domainIdInChildreanList = getDomainChildIds(dedicatedPodOfCluster.getDomainId()).contains(domainId); + //can dedicate a cluster to an account/domain if pod is dedicated to parent-domain + if (dedicatedPodOfCluster.getAccountId() != null || (accountId == null && !domainIdInChildreanList) || + (accountId != null && !(dedicatedPodOfCluster.getDomainId().equals(domainId) || domainIdInChildreanList))) { + s_logger.error("Cannot dedicate Cluster. Its Pod is already dedicated"); + HostPodVO pod = _podDao.findById(cluster.getPodId()); + throw new CloudRuntimeException("Cluster's Pod " + pod.getName() + " is already dedicated"); + } + } + + if (dedicatedZoneOfCluster != null) { + boolean domainIdInChildreanList = getDomainChildIds(dedicatedZoneOfCluster.getDomainId()).contains(domainId); + //can dedicate a cluster to an account/domain if zone is dedicated to parent-domain + if (dedicatedZoneOfCluster.getAccountId() != null || (accountId == null && !domainIdInChildreanList) || + (accountId != null && !(dedicatedZoneOfCluster.getDomainId().equals(domainId) || domainIdInChildreanList))) { + s_logger.error("Cannot dedicate Cluster. Its zone is already dedicated"); + DataCenterVO zone = _zoneDao.findById(cluster.getDataCenterId()); + throw new CloudRuntimeException("Cluster's Zone " + zone.getName() + " is already dedicated"); + } + } + + //check if any resource under this cluster is dedicated to different account or sub-domain + hosts = _hostDao.findByClusterId(cluster.getId()); + List hostsToRelease = new ArrayList(); + for (HostVO host : hosts) { + DedicatedResourceVO dHost = _dedicatedDao.findByHostId(host.getId()); + if (dHost != null) { + if (!(childDomainIds.contains(dHost.getDomainId()))) { + throw new CloudRuntimeException("Host " + host.getName() + " under this Cluster " + cluster.getName() + + " is dedicated to different account/domain"); + } + /*if all dedicated resources belongs to same account and domain then we should release dedication + and make new entry for this cluster */ + if (accountId != null) { + if (dHost.getAccountId().equals(accountId)) { + hostsToRelease.add(dHost); + } else { + s_logger.error("Cannot dedicate Cluster " + cluster.getName() + " to account" + accountName); + throw new CloudRuntimeException("Cannot dedicate Cluster " + cluster.getName() + " to account" + accountName); + } + } else { + if (dHost.getAccountId() == null && dHost.getDomainId().equals(domainId)) { + hostsToRelease.add(dHost); + } + } + } + } + + for (DedicatedResourceVO dr : hostsToRelease) { + releaseDedicatedResource(null, null, null, dr.getHostId()); + } + } + + checkHostsSuitabilityForExplicitDedication(accountId, childDomainIds, hosts); + + final Long accountIdFinal = accountId; + return Transaction.execute(new TransactionCallback>() { + @Override + public List doInTransaction(TransactionStatus status) { + // find or create the affinity group by name under this account/domain + AffinityGroup group = findOrCreateDedicatedAffinityGroup(domainId, accountIdFinal); + if (group == null) { + s_logger.error("Unable to dedicate zone due to, failed to create dedication affinity group"); + throw new CloudRuntimeException("Failed to dedicate zone. Please contact Cloud Support."); + } + DedicatedResourceVO dedicatedResource = new DedicatedResourceVO(null, null, clusterId, null, null, null, group.getId()); + try { + dedicatedResource.setDomainId(domainId); + if (accountIdFinal != null) { + dedicatedResource.setAccountId(accountIdFinal); + } + dedicatedResource = _dedicatedDao.persist(dedicatedResource); + } catch (Exception e) { + s_logger.error("Unable to dedicate host due to " + e.getMessage(), e); + throw new CloudRuntimeException("Failed to dedicate cluster. Please contact Cloud Support."); + } + + List result = new ArrayList(); + result.add(dedicatedResource); + return result; + } + }); + } + + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_DEDICATE_RESOURCE, eventDescription = "dedicating a Host") + public List dedicateHost(final Long hostId, final Long domainId, final String accountName) { + Long accountId = null; + if (accountName != null) { + Account caller = CallContext.current().getCallingAccount(); + Account owner = _accountMgr.finalizeOwner(caller, accountName, domainId, null); + accountId = owner.getId(); + } + checkAccountAndDomain(accountId, domainId); + HostVO host = _hostDao.findById(hostId); + if (host == null) { + throw new InvalidParameterValueException("Unable to find host by id " + hostId); + } else { + //check if host is of routing type + if (host.getType() != Host.Type.Routing) { + throw new CloudRuntimeException("Invalid host type for host " + host.getName()); + } + + DedicatedResourceVO dedicatedHost = _dedicatedDao.findByHostId(hostId); + DedicatedResourceVO dedicatedClusterOfHost = _dedicatedDao.findByClusterId(host.getClusterId()); + DedicatedResourceVO dedicatedPodOfHost = _dedicatedDao.findByPodId(host.getPodId()); + DedicatedResourceVO dedicatedZoneOfHost = _dedicatedDao.findByZoneId(host.getDataCenterId()); + + if (dedicatedHost != null) { + s_logger.error("Host " + host.getName() + " is already dedicated"); + throw new CloudRuntimeException("Host " + host.getName() + " is already dedicated"); + } + + if (dedicatedClusterOfHost != null) { + boolean domainIdInChildreanList = getDomainChildIds(dedicatedClusterOfHost.getDomainId()).contains(domainId); + //can dedicate a host to an account/domain if cluster is dedicated to parent-domain + if (dedicatedClusterOfHost.getAccountId() != null || (accountId == null && !domainIdInChildreanList) || + (accountId != null && !(dedicatedClusterOfHost.getDomainId().equals(domainId) || domainIdInChildreanList))) { + ClusterVO cluster = _clusterDao.findById(host.getClusterId()); + s_logger.error("Host's Cluster " + cluster.getName() + " is already dedicated"); + throw new CloudRuntimeException("Host's Cluster " + cluster.getName() + " is already dedicated"); + } + } + + if (dedicatedPodOfHost != null) { + boolean domainIdInChildreanList = getDomainChildIds(dedicatedPodOfHost.getDomainId()).contains(domainId); + //can dedicate a host to an account/domain if pod is dedicated to parent-domain + if (dedicatedPodOfHost.getAccountId() != null || (accountId == null && !domainIdInChildreanList) || + (accountId != null && !(dedicatedPodOfHost.getDomainId().equals(domainId) || domainIdInChildreanList))) { + HostPodVO pod = _podDao.findById(host.getPodId()); + s_logger.error("Host's Pod " + pod.getName() + " is already dedicated"); + throw new CloudRuntimeException("Host's Pod " + pod.getName() + " is already dedicated"); + } + } + + if (dedicatedZoneOfHost != null) { + boolean domainIdInChildreanList = getDomainChildIds(dedicatedZoneOfHost.getDomainId()).contains(domainId); + //can dedicate a host to an account/domain if zone is dedicated to parent-domain + if (dedicatedZoneOfHost.getAccountId() != null || (accountId == null && !domainIdInChildreanList) || + (accountId != null && !(dedicatedZoneOfHost.getDomainId().equals(domainId) || domainIdInChildreanList))) { + DataCenterVO zone = _zoneDao.findById(host.getDataCenterId()); + s_logger.error("Host's Data Center " + zone.getName() + " is already dedicated"); + throw new CloudRuntimeException("Host's Data Center " + zone.getName() + " is already dedicated"); + } + } + } + + List childDomainIds = getDomainChildIds(domainId); + childDomainIds.add(domainId); + checkHostSuitabilityForExplicitDedication(accountId, childDomainIds, hostId); + + final Long accountIdFinal = accountId; + return Transaction.execute(new TransactionCallback>() { + @Override + public List doInTransaction(TransactionStatus status) { + // find or create the affinity group by name under this account/domain + AffinityGroup group = findOrCreateDedicatedAffinityGroup(domainId, accountIdFinal); + if (group == null) { + s_logger.error("Unable to dedicate zone due to, failed to create dedication affinity group"); + throw new CloudRuntimeException("Failed to dedicate zone. Please contact Cloud Support."); + } + DedicatedResourceVO dedicatedResource = new DedicatedResourceVO(null, null, null, hostId, null, null, group.getId()); + try { + dedicatedResource.setDomainId(domainId); + if (accountIdFinal != null) { + dedicatedResource.setAccountId(accountIdFinal); + } + dedicatedResource = _dedicatedDao.persist(dedicatedResource); + } catch (Exception e) { + s_logger.error("Unable to dedicate host due to " + e.getMessage(), e); + throw new CloudRuntimeException("Failed to dedicate host. Please contact Cloud Support."); + } + + List result = new ArrayList(); + result.add(dedicatedResource); + return result; + } + }); + + } + + private AffinityGroup findOrCreateDedicatedAffinityGroup(Long domainId, Long accountId) { + if (domainId == null) { + return null; + } + + AffinityGroup group = null; + String accountName = null; + String affinityGroupName = null; + + if (accountId != null) { + AccountVO account = _accountDao.findById(accountId); + accountName = account.getAccountName(); + + group = _affinityGroupDao.findByAccountAndType(accountId, "ExplicitDedication"); + if (group != null) { + return group; + } + // default to a groupname with account/domain information + affinityGroupName = "DedicatedGrp-" + accountName; + + } else { + // domain level group + group = _affinityGroupDao.findDomainLevelGroupByType(domainId, "ExplicitDedication"); + if (group != null) { + return group; + } + // default to a groupname with account/domain information + String domainName = _domainDao.findById(domainId).getName(); + affinityGroupName = "DedicatedGrp-domain-" + domainName; + } + + group = _affinityGroupService.createAffinityGroup(accountName, null, domainId, affinityGroupName, "ExplicitDedication", "dedicated resources group"); + + return group; + + } + + private List getVmsOnHost(long hostId) { + List vms = _userVmDao.listUpByHostId(hostId); + List vmsByLastHostId = _userVmDao.listByLastHostId(hostId); + if (vmsByLastHostId.size() > 0) { + // check if any VMs are within skip.counting.hours, if yes we have to consider the host. + for (UserVmVO stoppedVM : vmsByLastHostId) { + long secondsSinceLastUpdate = (DateUtil.currentGMTTime().getTime() - stoppedVM.getUpdateTime().getTime()) / 1000; + if (secondsSinceLastUpdate < capacityReleaseInterval) { + vms.add(stoppedVM); + } + } + } + + return vms; + } + + private boolean checkHostSuitabilityForExplicitDedication(Long accountId, List domainIds, long hostId) { + boolean suitable = true; + List allVmsOnHost = getVmsOnHost(hostId); + if (accountId != null) { + for (UserVmVO vm : allVmsOnHost) { + if (vm.getAccountId() != accountId) { + s_logger.info("Host " + vm.getHostId() + " found to be unsuitable for explicit dedication as it is " + "running instances of another account"); + throw new CloudRuntimeException("Host " + hostId + " found to be unsuitable for explicit dedication as it is " + + "running instances of another account"); + } + } + } else { + for (UserVmVO vm : allVmsOnHost) { + if (!domainIds.contains(vm.getDomainId())) { + s_logger.info("Host " + vm.getHostId() + " found to be unsuitable for explicit dedication as it is " + "running instances of another domain"); + throw new CloudRuntimeException("Host " + hostId + " found to be unsuitable for explicit dedication as it is " + + "running instances of another domain"); + } + } + } + return suitable; + } + + private boolean checkHostsSuitabilityForExplicitDedication(Long accountId, List domainIds, List hosts) { + boolean suitable = true; + for (HostVO host : hosts) { + checkHostSuitabilityForExplicitDedication(accountId, domainIds, host.getId()); + } + return suitable; + } + + private void checkAccountAndDomain(Long accountId, Long domainId) { + DomainVO domain = _domainDao.findById(domainId); + if (domain == null) { + throw new InvalidParameterValueException("Unable to find the domain by id " + domainId + ", please specify valid domainId"); + } + //check if account belongs to the domain id + if (accountId != null) { + AccountVO account = _accountDao.findById(accountId); + if (account == null || domainId != account.getDomainId()) { + throw new InvalidParameterValueException("Please specify the domain id of the account id " + accountId); + } + } + } + + private List getDomainChildIds(long domainId) { + DomainVO domainRecord = _domainDao.findById(domainId); + List domainIds = new ArrayList(); + domainIds.add(domainRecord.getId()); + // find all domain Ids till leaf + List allChildDomains = _domainDao.findAllChildren(domainRecord.getPath(), domainRecord.getId()); + for (DomainVO domain : allChildDomains) { + domainIds.add(domain.getId()); + } + return domainIds; + } + + @Override + public DedicateZoneResponse createDedicateZoneResponse(DedicatedResources resource) { + DedicateZoneResponse dedicateZoneResponse = new DedicateZoneResponse(); + DataCenterVO dc = _zoneDao.findById(resource.getDataCenterId()); + DomainVO domain = _domainDao.findById(resource.getDomainId()); + AccountVO account = _accountDao.findById(resource.getAccountId()); + AffinityGroup group = _affinityGroupDao.findById(resource.getAffinityGroupId()); + dedicateZoneResponse.setId(resource.getUuid()); + dedicateZoneResponse.setZoneId(dc.getUuid()); + dedicateZoneResponse.setZoneName(dc.getName()); + dedicateZoneResponse.setDomainId(domain.getUuid()); + dedicateZoneResponse.setAffinityGroupId(group.getUuid()); + if (account != null) { + dedicateZoneResponse.setAccountId(account.getUuid()); + } + dedicateZoneResponse.setObjectName("dedicatedzone"); + return dedicateZoneResponse; + } + + @Override + public DedicatePodResponse createDedicatePodResponse(DedicatedResources resource) { + DedicatePodResponse dedicatePodResponse = new DedicatePodResponse(); + HostPodVO pod = _podDao.findById(resource.getPodId()); + DomainVO domain = _domainDao.findById(resource.getDomainId()); + AccountVO account = _accountDao.findById(resource.getAccountId()); + AffinityGroup group = _affinityGroupDao.findById(resource.getAffinityGroupId()); + dedicatePodResponse.setId(resource.getUuid()); + dedicatePodResponse.setPodId(pod.getUuid()); + dedicatePodResponse.setPodName(pod.getName()); + dedicatePodResponse.setDomainId(domain.getUuid()); + dedicatePodResponse.setAffinityGroupId(group.getUuid()); + if (account != null) { + dedicatePodResponse.setAccountId(account.getUuid()); + } + dedicatePodResponse.setObjectName("dedicatedpod"); + return dedicatePodResponse; + } + + @Override + public DedicateClusterResponse createDedicateClusterResponse(DedicatedResources resource) { + DedicateClusterResponse dedicateClusterResponse = new DedicateClusterResponse(); + ClusterVO cluster = _clusterDao.findById(resource.getClusterId()); + DomainVO domain = _domainDao.findById(resource.getDomainId()); + AccountVO account = _accountDao.findById(resource.getAccountId()); + AffinityGroup group = _affinityGroupDao.findById(resource.getAffinityGroupId()); + dedicateClusterResponse.setId(resource.getUuid()); + dedicateClusterResponse.setClusterId(cluster.getUuid()); + dedicateClusterResponse.setClusterName(cluster.getName()); + dedicateClusterResponse.setDomainId(domain.getUuid()); + dedicateClusterResponse.setAffinityGroupId(group.getUuid()); + if (account != null) { + dedicateClusterResponse.setAccountId(account.getUuid()); + } + dedicateClusterResponse.setObjectName("dedicatedcluster"); + return dedicateClusterResponse; + } + + @Override + public DedicateHostResponse createDedicateHostResponse(DedicatedResources resource) { + DedicateHostResponse dedicateHostResponse = new DedicateHostResponse(); + HostVO host = _hostDao.findById(resource.getHostId()); + DomainVO domain = _domainDao.findById(resource.getDomainId()); + AccountVO account = _accountDao.findById(resource.getAccountId()); + AffinityGroup group = _affinityGroupDao.findById(resource.getAffinityGroupId()); + dedicateHostResponse.setId(resource.getUuid()); + dedicateHostResponse.setHostId(host.getUuid()); + dedicateHostResponse.setHostName(host.getName()); + dedicateHostResponse.setDomainId(domain.getUuid()); + dedicateHostResponse.setAffinityGroupId(group.getUuid()); + if (account != null) { + dedicateHostResponse.setAccountId(account.getUuid()); + } + dedicateHostResponse.setObjectName("dedicatedhost"); + return dedicateHostResponse; + } + + @Override + public List> getCommands() { + List> cmdList = new ArrayList>(); + cmdList.add(DedicateZoneCmd.class); + cmdList.add(DedicatePodCmd.class); + cmdList.add(DedicateClusterCmd.class); + cmdList.add(DedicateHostCmd.class); + cmdList.add(ListDedicatedZonesCmd.class); + cmdList.add(ListDedicatedPodsCmd.class); + cmdList.add(ListDedicatedClustersCmd.class); + cmdList.add(ListDedicatedHostsCmd.class); + cmdList.add(ReleaseDedicatedClusterCmd.class); + cmdList.add(ReleaseDedicatedHostCmd.class); + cmdList.add(ReleaseDedicatedPodCmd.class); + cmdList.add(ReleaseDedicatedZoneCmd.class); + return cmdList; + } + + @Override + public Pair, Integer> listDedicatedZones(ListDedicatedZonesCmd cmd) { + Long zoneId = cmd.getZoneId(); + Long domainId = cmd.getDomainId(); + String accountName = cmd.getAccountName(); + Long accountId = null; + Long affinityGroupId = cmd.getAffinityGroupId(); + + if (accountName != null) { + if (domainId != null) { + Account account = _accountDao.findActiveAccount(accountName, domainId); + if (account != null) { + accountId = account.getId(); + } + } else { + throw new InvalidParameterValueException("Please specify the domain id of the account: " + accountName); + } + } + Pair, Integer> result = _dedicatedDao.searchDedicatedZones(zoneId, domainId, accountId, affinityGroupId); + return new Pair, Integer>(result.first(), result.second()); + } + + @Override + public Pair, Integer> listDedicatedPods(ListDedicatedPodsCmd cmd) { + Long podId = cmd.getPodId(); + Long domainId = cmd.getDomainId(); + String accountName = cmd.getAccountName(); + Long accountId = null; + Long affinityGroupId = cmd.getAffinityGroupId(); + + if (accountName != null) { + if (domainId != null) { + Account account = _accountDao.findActiveAccount(accountName, domainId); + if (account != null) { + accountId = account.getId(); + } + } else { + throw new InvalidParameterValueException("Please specify the domain id of the account: " + accountName); + } + } + Pair, Integer> result = _dedicatedDao.searchDedicatedPods(podId, domainId, accountId, affinityGroupId); + return new Pair, Integer>(result.first(), result.second()); + } + + @Override + public Pair, Integer> listDedicatedClusters(ListDedicatedClustersCmd cmd) { + Long clusterId = cmd.getClusterId(); + Long domainId = cmd.getDomainId(); + String accountName = cmd.getAccountName(); + Long accountId = null; + Long affinityGroupId = cmd.getAffinityGroupId(); + + if (accountName != null) { + if (domainId != null) { + Account account = _accountDao.findActiveAccount(accountName, domainId); + if (account != null) { + accountId = account.getId(); + } + } else { + throw new InvalidParameterValueException("Please specify the domain id of the account: " + accountName); + } + } + Pair, Integer> result = _dedicatedDao.searchDedicatedClusters(clusterId, domainId, accountId, affinityGroupId); + return new Pair, Integer>(result.first(), result.second()); + } + + @Override + public Pair, Integer> listDedicatedHosts(ListDedicatedHostsCmd cmd) { + Long hostId = cmd.getHostId(); + Long domainId = cmd.getDomainId(); + String accountName = cmd.getAccountName(); + Long affinityGroupId = cmd.getAffinityGroupId(); + + Long accountId = null; + if (accountName != null) { + if (domainId != null) { + Account account = _accountDao.findActiveAccount(accountName, domainId); + if (account != null) { + accountId = account.getId(); + } + } else { + throw new InvalidParameterValueException("Please specify the domain id of the account: " + accountName); + } + } + + Pair, Integer> result = _dedicatedDao.searchDedicatedHosts(hostId, domainId, accountId, affinityGroupId); + return new Pair, Integer>(result.first(), result.second()); + } + + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_DEDICATE_RESOURCE_RELEASE, eventDescription = "Releasing dedicated resource") + public boolean releaseDedicatedResource(final Long zoneId, Long podId, Long clusterId, Long hostId) throws InvalidParameterValueException { + DedicatedResourceVO resource = null; + if (zoneId != null) { + resource = _dedicatedDao.findByZoneId(zoneId); + } + if (podId != null) { + resource = _dedicatedDao.findByPodId(podId); + } + if (clusterId != null) { + resource = _dedicatedDao.findByClusterId(clusterId); + } + if (hostId != null) { + resource = _dedicatedDao.findByHostId(hostId); + } + if (resource == null) { + throw new InvalidParameterValueException("No Dedicated Resource available to release"); + } else { + final DedicatedResourceVO resourceFinal = resource; + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + Long resourceId = resourceFinal.getId(); + if (!_dedicatedDao.remove(resourceId)) { + throw new CloudRuntimeException("Failed to delete Resource " + resourceId); + } + if (zoneId != null) { + // remove the domainId set in zone + DataCenterVO dc = _zoneDao.findById(zoneId); + if (dc != null) { + dc.setDomainId(null); + dc.setDomain(null); + if (!_zoneDao.update(zoneId, dc)) { + throw new CloudRuntimeException("Failed to release dedicated zone, could not clear domainId. Please contact Cloud Support."); + } + } + } + } + }); + + // find the group associated and check if there are any more + // resources under that group + List resourcesInGroup = _dedicatedDao.listByAffinityGroupId(resource.getAffinityGroupId()); + if (resourcesInGroup.isEmpty()) { + // delete the group + _affinityGroupService.deleteAffinityGroup(resource.getAffinityGroupId(), null, null, null, null); + } + + } + return true; + } +} diff --git a/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/dedicated/DedicatedService.java b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/dedicated/DedicatedService.java new file mode 100644 index 0000000000..6ca8ec7d11 --- /dev/null +++ b/cosmic-core/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/dedicated/DedicatedService.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 org.apache.cloudstack.dedicated; + +import java.util.List; + +import com.cloud.dc.DedicatedResourceVO; +import com.cloud.dc.DedicatedResources; +import com.cloud.utils.Pair; +import com.cloud.utils.component.PluggableService; + +import org.apache.cloudstack.api.commands.ListDedicatedClustersCmd; +import org.apache.cloudstack.api.commands.ListDedicatedHostsCmd; +import org.apache.cloudstack.api.commands.ListDedicatedPodsCmd; +import org.apache.cloudstack.api.commands.ListDedicatedZonesCmd; +import org.apache.cloudstack.api.response.DedicateClusterResponse; +import org.apache.cloudstack.api.response.DedicateHostResponse; +import org.apache.cloudstack.api.response.DedicatePodResponse; +import org.apache.cloudstack.api.response.DedicateZoneResponse; + +public interface DedicatedService extends PluggableService { + + DedicatePodResponse createDedicatePodResponse(DedicatedResources resource); + + DedicateClusterResponse createDedicateClusterResponse(DedicatedResources resource); + + DedicateHostResponse createDedicateHostResponse(DedicatedResources resource); + + Pair, Integer> listDedicatedPods(ListDedicatedPodsCmd cmd); + + Pair, Integer> listDedicatedHosts(ListDedicatedHostsCmd cmd); + + Pair, Integer> listDedicatedClusters(ListDedicatedClustersCmd cmd); + + boolean releaseDedicatedResource(Long zoneId, Long podId, Long clusterId, Long hostId); + + DedicateZoneResponse createDedicateZoneResponse(DedicatedResources resource); + + Pair, Integer> listDedicatedZones(ListDedicatedZonesCmd cmd); + + List dedicateZone(Long zoneId, Long domainId, String accountName); + + List dedicatePod(Long podId, Long domainId, String accountName); + + List dedicateCluster(Long clusterId, Long domainId, String accountName); + + List dedicateHost(Long hostId, Long domainId, String accountName); + +} diff --git a/cosmic-core/plugins/dedicated-resources/src/main/resources/META-INF/cloudstack/core/spring-dedicated-resources-core-context.xml b/cosmic-core/plugins/dedicated-resources/src/main/resources/META-INF/cloudstack/core/spring-dedicated-resources-core-context.xml new file mode 100644 index 0000000000..e2879f7108 --- /dev/null +++ b/cosmic-core/plugins/dedicated-resources/src/main/resources/META-INF/cloudstack/core/spring-dedicated-resources-core-context.xml @@ -0,0 +1,33 @@ + + + + + + diff --git a/cosmic-core/plugins/dedicated-resources/src/test/java/org/apache/cloudstack/dedicated/manager/DedicatedApiUnitTest.java b/cosmic-core/plugins/dedicated-resources/src/test/java/org/apache/cloudstack/dedicated/manager/DedicatedApiUnitTest.java new file mode 100644 index 0000000000..a3a5437fb1 --- /dev/null +++ b/cosmic-core/plugins/dedicated-resources/src/test/java/org/apache/cloudstack/dedicated/manager/DedicatedApiUnitTest.java @@ -0,0 +1,335 @@ +// 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 org.apache.cloudstack.dedicated.manager; + +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.util.UUID; + +import javax.inject.Inject; + +import com.cloud.dc.DedicatedResourceVO; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.dc.dao.DedicatedResourceDao; +import com.cloud.dc.dao.HostPodDao; +import com.cloud.domain.DomainVO; +import com.cloud.domain.dao.DomainDao; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.host.dao.HostDao; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.AccountVO; +import com.cloud.user.User; +import com.cloud.user.UserVO; +import com.cloud.user.dao.AccountDao; +import com.cloud.utils.component.ComponentContext; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.dao.UserVmDao; + +import org.apache.cloudstack.affinity.AffinityGroupService; +import org.apache.cloudstack.affinity.dao.AffinityGroupDao; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.dedicated.DedicatedResourceManagerImpl; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.test.utils.SpringUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.filter.TypeFilter; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +import junit.framework.Assert; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(loader = AnnotationConfigContextLoader.class) +public class DedicatedApiUnitTest { + public static final Logger s_logger = LoggerFactory.getLogger(DedicatedApiUnitTest.class); + @Inject + DedicatedResourceManagerImpl _dedicatedService = new DedicatedResourceManagerImpl(); + + @Inject + AccountManager _acctMgr; + + @Inject + AccountDao _accountDao; + + @Inject + DomainDao _domainDao; + + @Inject + UserVmDao _vmDao; + + @Inject + DedicatedResourceDao _dedicatedDao; + + @Inject + DataCenterDao _dcDao; + + @Inject + HostPodDao _podDao; + + @Inject + ClusterDao _clusterDao; + + @Inject + HostDao _hostDao; + + @Inject + ConfigurationDao _configDao; + + private static long domainId = 5L; + private static long accountId = 5L; + private static String accountName = "admin"; + + @Before + public void setUp() { + ComponentContext.initComponentsLifeCycle(); + AccountVO account = new AccountVO(accountName, domainId, "networkDomain", Account.ACCOUNT_TYPE_NORMAL, "uuid"); + DomainVO domain = new DomainVO("rootDomain", 5L, 5L, "networkDomain"); + + UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN); + + CallContext.register(user, account); + when(_acctMgr.finalizeOwner((Account)anyObject(), anyString(), anyLong(), anyLong())).thenReturn(account); + when(_accountDao.findByIdIncludingRemoved(0L)).thenReturn(account); + when(_accountDao.findById(anyLong())).thenReturn(account); + when(_domainDao.findById(domainId)).thenReturn(domain); + } + + @After + public void tearDown() { + CallContext.unregister(); + } + + @Test(expected = InvalidParameterValueException.class) + public void InvalidDomainIDForAccountTest() { + _dedicatedService.dedicateZone(10L, domainId, accountName); + } + + @Test(expected = InvalidParameterValueException.class) + public void dedicateResourceInvalidAccountIDTest() { + _dedicatedService.dedicateZone(10L, domainId, accountName); + } + + @Test + public void releaseDedicatedZoneInvalidIdTest() { + when(_dedicatedDao.findByZoneId(10L)).thenReturn(null); + try { + _dedicatedService.releaseDedicatedResource(10L, null, null, null); + } catch (InvalidParameterValueException e) { + Assert.assertTrue(e.getMessage().contains("No Dedicated Resource available to release")); + } + } + + /* @Test + public void runDedicateZoneTest() { + DataCenterVO dc = new DataCenterVO(10L, "TestZone", "Dedicated", + "8.8.8.8", null, "10.0.0.1", null, "10.0.0.1/24", null, null, + NetworkType.Basic, null, null); + when(_dcDao.findById(10L)).thenReturn(dc); + try { + List result = _dedicatedService.dedicateZone(10L, domainId, accountName); + Assert.assertNotNull(result); + } catch (Exception e) { + s_logger.info("exception in testing dedication of zone " + + e.toString()); + } + } + + @Test + public void runDedicatePodTest() { + HostPodVO pod = new HostPodVO("TestPod", 20L, "10.0.0.1", "10.0.0.0", + 22, null); + when(_podDao.findById(10L)).thenReturn(pod); + try { + List result = _dedicatedService.dedicatePod(10L, domainId, accountName); + Assert.assertNotNull(result); + } catch (Exception e) { + s_logger.info("exception in testing dedication of pod " + + e.toString()); + } + } + + @Test + public void runDedicateClusterTest() { + ClusterVO cluster = new ClusterVO(10L, 10L, "TestCluster"); + when(_clusterDao.findById(10L)).thenReturn(cluster); + try { + List result = _dedicatedService.dedicateCluster(10L, domainId, accountName); + Assert.assertNotNull(result); + } catch (Exception e) { + s_logger.info("exception in testing dedication of cluster " + + e.toString()); + } + } + + @Test + public void runDedicateHostTest() { + HostVO host = new HostVO(10L, "Host-1", Host.Type.Routing, null, + "10.0.0.0", null, null, null, null, null, null, null, null, + Status.Up, null, null, null, 10L, 10L, 30L, 10233, null, null, + null, 0, null); + when(_hostDao.findById(10L)).thenReturn(host); + try { + List result = _dedicatedService.dedicateHost(10L, domainId, accountName); + Assert.assertNotNull(result); + } catch (Exception e) { + s_logger.info("exception in testing dedication of host " + + e.toString()); + } + } + */ + + @Test(expected = CloudRuntimeException.class) + public void dedicateZoneExistTest() { + DedicatedResourceVO dr = new DedicatedResourceVO(10L, null, null, null, domainId, accountId, 12L); + when(_dedicatedDao.findByZoneId(10L)).thenReturn(dr); + _dedicatedService.dedicateZone(10L, domainId, accountName); + } + + @Test(expected = CloudRuntimeException.class) + public void dedicatePodExistTest() { + DedicatedResourceVO dr = new DedicatedResourceVO(null, 10L, null, null, domainId, accountId, 12L); + when(_dedicatedDao.findByPodId(10L)).thenReturn(dr); + _dedicatedService.dedicatePod(10L, domainId, accountName); + } + + @Test(expected = CloudRuntimeException.class) + public void dedicateClusterExistTest() { + DedicatedResourceVO dr = new DedicatedResourceVO(null, null, 10L, null, domainId, accountId, 12L); + when(_dedicatedDao.findByClusterId(10L)).thenReturn(dr); + _dedicatedService.dedicateCluster(10L, domainId, accountName); + } + + @Test(expected = CloudRuntimeException.class) + public void dedicateHostExistTest() { + DedicatedResourceVO dr = new DedicatedResourceVO(null, null, null, 10L, domainId, accountId, 12L); + when(_dedicatedDao.findByHostId(10L)).thenReturn(dr); + _dedicatedService.dedicateHost(10L, domainId, accountName); + } + + @Test(expected = InvalidParameterValueException.class) + public void releaseDedicatedPodInvalidIdTest() { + when(_dedicatedDao.findByPodId(10L)).thenReturn(null); + _dedicatedService.releaseDedicatedResource(null, 10L, null, null); + } + + @Test(expected = InvalidParameterValueException.class) + public void releaseDedicatedClusterInvalidIdTest() { + when(_dedicatedDao.findByClusterId(10L)).thenReturn(null); + _dedicatedService.releaseDedicatedResource(null, null, 10L, null); + } + + @Test(expected = InvalidParameterValueException.class) + public void releaseDedicatedHostInvalidIdTest() { + when(_dedicatedDao.findByHostId(10L)).thenReturn(null); + _dedicatedService.releaseDedicatedResource(null, null, null, 10L); + } + + @Configuration + @ComponentScan(basePackageClasses = {DedicatedResourceManagerImpl.class}, + includeFilters = {@Filter(value = TestConfiguration.Library.class, type = FilterType.CUSTOM)}, + useDefaultFilters = false) + public static class TestConfiguration extends SpringUtils.CloudStackTestConfiguration { + + @Bean + public AccountDao accountDao() { + return Mockito.mock(AccountDao.class); + } + + @Bean + public DomainDao domainDao() { + return Mockito.mock(DomainDao.class); + } + + @Bean + public DedicatedResourceDao dedicatedDao() { + return Mockito.mock(DedicatedResourceDao.class); + } + + @Bean + public HostDao hostDao() { + return Mockito.mock(HostDao.class); + } + + @Bean + public AccountManager acctManager() { + return Mockito.mock(AccountManager.class); + } + + @Bean + public UserVmDao userVmDao() { + return Mockito.mock(UserVmDao.class); + } + + @Bean + public DataCenterDao dataCenterDao() { + return Mockito.mock(DataCenterDao.class); + } + + @Bean + public HostPodDao hostPodDao() { + return Mockito.mock(HostPodDao.class); + } + + @Bean + public ClusterDao clusterDao() { + return Mockito.mock(ClusterDao.class); + } + + @Bean + public ConfigurationDao configDao() { + return Mockito.mock(ConfigurationDao.class); + } + + @Bean + public AffinityGroupService affinityGroupService() { + return Mockito.mock(AffinityGroupService.class); + } + + @Bean + public AffinityGroupDao affinityGroupDao() { + return Mockito.mock(AffinityGroupDao.class); + } + + public static class Library implements TypeFilter { + @Override + public boolean match(MetadataReader mdr, MetadataReaderFactory arg1) throws IOException { + ComponentScan cs = TestConfiguration.class.getAnnotation(ComponentScan.class); + return SpringUtils.includedInBasePackageClasses(mdr.getClassMetadata().getClassName(), cs); + } + } + } +} diff --git a/cosmic-core/plugins/dedicated-resources/src/test/resource/dedicatedContext.xml b/cosmic-core/plugins/dedicated-resources/src/test/resource/dedicatedContext.xml new file mode 100644 index 0000000000..9ce8362d4b --- /dev/null +++ b/cosmic-core/plugins/dedicated-resources/src/test/resource/dedicatedContext.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cosmic-core/plugins/deployment-planners/implicit-dedication/pom.xml b/cosmic-core/plugins/deployment-planners/implicit-dedication/pom.xml new file mode 100644 index 0000000000..ce86d0a34f --- /dev/null +++ b/cosmic-core/plugins/deployment-planners/implicit-dedication/pom.xml @@ -0,0 +1,11 @@ + + 4.0.0 + cloud-plugin-planner-implicit-dedication + Cosmic Plugin - Implicit Dedication Planner + + cloud.cosmic + cosmic-plugins + 5.1.0.0-SNAPSHOT + ../../pom.xml + + \ No newline at end of file diff --git a/cosmic-core/plugins/deployment-planners/implicit-dedication/src/main/java/com/cloud/deploy/ImplicitDedicationPlanner.java b/cosmic-core/plugins/deployment-planners/implicit-dedication/src/main/java/com/cloud/deploy/ImplicitDedicationPlanner.java new file mode 100644 index 0000000000..c12ce3314a --- /dev/null +++ b/cosmic-core/plugins/deployment-planners/implicit-dedication/src/main/java/com/cloud/deploy/ImplicitDedicationPlanner.java @@ -0,0 +1,318 @@ +// 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.deploy; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import com.cloud.configuration.Config; +import com.cloud.exception.InsufficientServerCapacityException; +import com.cloud.host.HostVO; +import com.cloud.resource.ResourceManager; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.service.dao.ServiceOfferingDetailsDao; +import com.cloud.user.Account; +import com.cloud.utils.DateUtil; +import com.cloud.utils.NumbersUtil; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachineProfile; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ImplicitDedicationPlanner extends FirstFitPlanner implements DeploymentClusterPlanner { + + private static final Logger s_logger = LoggerFactory.getLogger(ImplicitDedicationPlanner.class); + + @Inject + private ServiceOfferingDao serviceOfferingDao; + @Inject + private ServiceOfferingDetailsDao serviceOfferingDetailsDao; + @Inject + private ResourceManager resourceMgr; + + private int capacityReleaseInterval; + + @Override + public boolean configure(final String name, final Map params) throws ConfigurationException { + super.configure(name, params); + capacityReleaseInterval = NumbersUtil.parseInt(configDao.getValue(Config.CapacitySkipcountingHours.key()), 3600); + return true; + } + + @Override + public List orderClusters(VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid) throws InsufficientServerCapacityException { + List clusterList = super.orderClusters(vmProfile, plan, avoid); + Set hostsToAvoid = avoid.getHostsToAvoid(); + Account account = vmProfile.getOwner(); + + if (clusterList == null || clusterList.isEmpty()) { + return clusterList; + } + + // Check if strict or preferred mode should be used. + boolean preferred = isServiceOfferingUsingPlannerInPreferredMode(vmProfile.getServiceOfferingId()); + + // Get the list of all the hosts in the given clusters + List allHosts = new ArrayList(); + for (Long cluster : clusterList) { + List hostsInCluster = resourceMgr.listAllHostsInCluster(cluster); + for (HostVO hostVO : hostsInCluster) { + allHosts.add(hostVO.getId()); + } + } + + // Go over all the hosts in the cluster and get a list of + // 1. All empty hosts, not running any vms. + // 2. Hosts running vms for this account and created by a service offering which uses an + // implicit dedication planner. + // 3. Hosts running vms created by implicit planner and in strict mode of other accounts. + // 4. Hosts running vms from other account or from this account but created by a service offering which uses + // any planner besides implicit. + Set emptyHosts = new HashSet(); + Set hostRunningVmsOfAccount = new HashSet(); + Set hostRunningStrictImplicitVmsOfOtherAccounts = new HashSet(); + Set allOtherHosts = new HashSet(); + for (Long host : allHosts) { + List vms = getVmsOnHost(host); + if (vms == null || vms.isEmpty()) { + emptyHosts.add(host); + } else if (checkHostSuitabilityForImplicitDedication(account.getAccountId(), vms)) { + hostRunningVmsOfAccount.add(host); + } else if (checkIfAllVmsCreatedInStrictMode(account.getAccountId(), vms)) { + hostRunningStrictImplicitVmsOfOtherAccounts.add(host); + } else { + allOtherHosts.add(host); + } + } + + // Hosts running vms of other accounts created by ab implicit planner in strict mode should always be avoided. + avoid.addHostList(hostRunningStrictImplicitVmsOfOtherAccounts); + + if (!hostRunningVmsOfAccount.isEmpty() && (hostsToAvoid == null || !hostsToAvoid.containsAll(hostRunningVmsOfAccount))) { + // Check if any of hosts that are running implicit dedicated vms are available (not in avoid list). + // If so, we'll try and use these hosts. + avoid.addHostList(emptyHosts); + avoid.addHostList(allOtherHosts); + clusterList = getUpdatedClusterList(clusterList, avoid.getHostsToAvoid()); + } else if (!emptyHosts.isEmpty() && (hostsToAvoid == null || !hostsToAvoid.containsAll(emptyHosts))) { + // If there aren't implicit resources try on empty hosts + avoid.addHostList(allOtherHosts); + clusterList = getUpdatedClusterList(clusterList, avoid.getHostsToAvoid()); + } else if (!preferred) { + // If in strict mode, there is nothing else to try. + clusterList = null; + } else { + // If in preferred mode, check if hosts are available to try, otherwise return an empty cluster list. + if (!allOtherHosts.isEmpty() && (hostsToAvoid == null || !hostsToAvoid.containsAll(allOtherHosts))) { + clusterList = getUpdatedClusterList(clusterList, avoid.getHostsToAvoid()); + } else { + clusterList = null; + } + } + + return clusterList; + } + + private List getVmsOnHost(long hostId) { + List vms = vmInstanceDao.listUpByHostId(hostId); + List vmsByLastHostId = vmInstanceDao.listByLastHostId(hostId); + if (vmsByLastHostId.size() > 0) { + // check if any VMs are within skip.counting.hours, if yes we have to consider the host. + for (VMInstanceVO stoppedVM : vmsByLastHostId) { + long secondsSinceLastUpdate = (DateUtil.currentGMTTime().getTime() - stoppedVM.getUpdateTime().getTime()) / 1000; + if (secondsSinceLastUpdate < capacityReleaseInterval) { + vms.add(stoppedVM); + } + } + } + + return vms; + } + + private boolean checkHostSuitabilityForImplicitDedication(Long accountId, List allVmsOnHost) { + boolean suitable = true; + if (allVmsOnHost.isEmpty()) + return false; + + for (VMInstanceVO vm : allVmsOnHost) { + if (vm.getAccountId() != accountId) { + s_logger.info("Host " + vm.getHostId() + " found to be unsuitable for implicit dedication as it is " + "running instances of another account"); + suitable = false; + break; + } else { + if (!isImplicitPlannerUsedByOffering(vm.getServiceOfferingId())) { + s_logger.info("Host " + vm.getHostId() + " found to be unsuitable for implicit dedication as it " + + "is running instances of this account which haven't been created using implicit dedication."); + suitable = false; + break; + } + } + } + return suitable; + } + + private boolean checkIfAllVmsCreatedInStrictMode(Long accountId, List allVmsOnHost) { + boolean createdByImplicitStrict = true; + if (allVmsOnHost.isEmpty()) + return false; + for (VMInstanceVO vm : allVmsOnHost) { + if (!isImplicitPlannerUsedByOffering(vm.getServiceOfferingId())) { + s_logger.info("Host " + vm.getHostId() + " found to be running a vm created by a planner other" + " than implicit."); + createdByImplicitStrict = false; + break; + } else if (isServiceOfferingUsingPlannerInPreferredMode(vm.getServiceOfferingId())) { + s_logger.info("Host " + vm.getHostId() + " found to be running a vm created by an implicit planner" + " in preferred mode."); + createdByImplicitStrict = false; + break; + } + } + return createdByImplicitStrict; + } + + private boolean isImplicitPlannerUsedByOffering(long offeringId) { + boolean implicitPlannerUsed = false; + ServiceOfferingVO offering = serviceOfferingDao.findByIdIncludingRemoved(offeringId); + if (offering == null) { + s_logger.error("Couldn't retrieve the offering by the given id : " + offeringId); + } else { + String plannerName = offering.getDeploymentPlanner(); + if (plannerName == null) { + plannerName = globalDeploymentPlanner; + } + + if (plannerName != null && this.getName().equals(plannerName)) { + implicitPlannerUsed = true; + } + } + + return implicitPlannerUsed; + } + + private boolean isServiceOfferingUsingPlannerInPreferredMode(long serviceOfferingId) { + boolean preferred = false; + Map details = serviceOfferingDetailsDao.listDetailsKeyPairs(serviceOfferingId); + if (details != null && !details.isEmpty()) { + String preferredAttribute = details.get("ImplicitDedicationMode"); + if (preferredAttribute != null && preferredAttribute.equals("Preferred")) { + preferred = true; + } + } + return preferred; + } + + private List getUpdatedClusterList(List clusterList, Set hostsSet) { + List updatedClusterList = new ArrayList(); + for (Long cluster : clusterList) { + List hosts = resourceMgr.listAllHostsInCluster(cluster); + Set hostsInClusterSet = new HashSet(); + for (HostVO host : hosts) { + hostsInClusterSet.add(host.getId()); + } + + if (!hostsSet.containsAll(hostsInClusterSet)) { + updatedClusterList.add(cluster); + } + } + + return updatedClusterList; + } + + @Override + public PlannerResourceUsage getResourceUsage(VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid) throws InsufficientServerCapacityException { + // Check if strict or preferred mode should be used. + boolean preferred = isServiceOfferingUsingPlannerInPreferredMode(vmProfile.getServiceOfferingId()); + + // If service offering in strict mode return resource usage as Dedicated + if (!preferred) { + return PlannerResourceUsage.Dedicated; + } else { + // service offering is in implicit mode. + // find is it possible to deploy in dedicated mode, + // if its possible return dedicated else return shared. + List clusterList = super.orderClusters(vmProfile, plan, avoid); + Set hostsToAvoid = avoid.getHostsToAvoid(); + Account account = vmProfile.getOwner(); + + // Get the list of all the hosts in the given clusters + List allHosts = new ArrayList(); + for (Long cluster : clusterList) { + List hostsInCluster = resourceMgr.listAllHostsInCluster(cluster); + for (HostVO hostVO : hostsInCluster) { + + allHosts.add(hostVO.getId()); + } + } + + // Go over all the hosts in the cluster and get a list of + // 1. All empty hosts, not running any vms. + // 2. Hosts running vms for this account and created by a service + // offering which uses an + // implicit dedication planner. + // 3. Hosts running vms created by implicit planner and in strict + // mode of other accounts. + // 4. Hosts running vms from other account or from this account but + // created by a service offering which uses + // any planner besides implicit. + Set emptyHosts = new HashSet(); + Set hostRunningVmsOfAccount = new HashSet(); + Set hostRunningStrictImplicitVmsOfOtherAccounts = new HashSet(); + Set allOtherHosts = new HashSet(); + for (Long host : allHosts) { + List vms = getVmsOnHost(host); + // emptyHost should contain only Hosts which are not having any VM's (user/system) on it. + if (vms == null || vms.isEmpty()) { + emptyHosts.add(host); + } else if (checkHostSuitabilityForImplicitDedication(account.getAccountId(), vms)) { + hostRunningVmsOfAccount.add(host); + } else if (checkIfAllVmsCreatedInStrictMode(account.getAccountId(), vms)) { + hostRunningStrictImplicitVmsOfOtherAccounts.add(host); + } else { + allOtherHosts.add(host); + } + } + + // Hosts running vms of other accounts created by ab implicit + // planner in strict mode should always be avoided. + avoid.addHostList(hostRunningStrictImplicitVmsOfOtherAccounts); + + if (!hostRunningVmsOfAccount.isEmpty() && (hostsToAvoid == null || !hostsToAvoid.containsAll(hostRunningVmsOfAccount))) { + // Check if any of hosts that are running implicit dedicated vms are available (not in avoid list). + // If so, we'll try and use these hosts. We can deploy in Dedicated mode + return PlannerResourceUsage.Dedicated; + } else if (!emptyHosts.isEmpty() && (hostsToAvoid == null || !hostsToAvoid.containsAll(emptyHosts))) { + // If there aren't implicit resources try on empty hosts, As empty hosts are available we can deploy in Dedicated mode. + // Empty hosts can contain hosts which are not having user vms but system vms are running. + // But the host where system vms are running is marked as shared and still be part of empty Hosts. + // The scenario will fail where actual Empty hosts and uservms not running host. + return PlannerResourceUsage.Dedicated; + } else { + if (!allOtherHosts.isEmpty() && (hostsToAvoid == null || !hostsToAvoid.containsAll(allOtherHosts))) { + return PlannerResourceUsage.Shared; + } + } + return PlannerResourceUsage.Shared; + } + } +} diff --git a/cosmic-core/plugins/deployment-planners/implicit-dedication/src/main/java/resources/META-INF/cloudstack/implicit-dedication/module.properties b/cosmic-core/plugins/deployment-planners/implicit-dedication/src/main/java/resources/META-INF/cloudstack/implicit-dedication/module.properties new file mode 100644 index 0000000000..6cda90463a --- /dev/null +++ b/cosmic-core/plugins/deployment-planners/implicit-dedication/src/main/java/resources/META-INF/cloudstack/implicit-dedication/module.properties @@ -0,0 +1,18 @@ +# 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. +name=implicit-dedication +parent=planner \ No newline at end of file diff --git a/cosmic-core/plugins/deployment-planners/implicit-dedication/src/main/java/resources/META-INF/cloudstack/implicit-dedication/spring-implicit-dedication-context.xml b/cosmic-core/plugins/deployment-planners/implicit-dedication/src/main/java/resources/META-INF/cloudstack/implicit-dedication/spring-implicit-dedication-context.xml new file mode 100644 index 0000000000..1c9a3c1401 --- /dev/null +++ b/cosmic-core/plugins/deployment-planners/implicit-dedication/src/main/java/resources/META-INF/cloudstack/implicit-dedication/spring-implicit-dedication-context.xml @@ -0,0 +1,21 @@ + + + + + + + + + diff --git a/cosmic-core/plugins/deployment-planners/implicit-dedication/src/test/java/org/apache/cloudstack/implicitplanner/ImplicitPlannerTest.java b/cosmic-core/plugins/deployment-planners/implicit-dedication/src/test/java/org/apache/cloudstack/implicitplanner/ImplicitPlannerTest.java new file mode 100644 index 0000000000..771b081246 --- /dev/null +++ b/cosmic-core/plugins/deployment-planners/implicit-dedication/src/test/java/org/apache/cloudstack/implicitplanner/ImplicitPlannerTest.java @@ -0,0 +1,595 @@ +// 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 org.apache.cloudstack.implicitplanner; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import javax.inject.Inject; + +import com.cloud.capacity.Capacity; +import com.cloud.capacity.CapacityManager; +import com.cloud.capacity.dao.CapacityDao; +import com.cloud.dc.ClusterDetailsDao; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.dc.dao.HostPodDao; +import com.cloud.deploy.DataCenterDeployment; +import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.deploy.ImplicitDedicationPlanner; +import com.cloud.exception.InsufficientServerCapacityException; +import com.cloud.gpu.dao.HostGpuGroupsDao; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.host.dao.HostTagsDao; +import com.cloud.resource.ResourceManager; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.service.dao.ServiceOfferingDetailsDao; +import com.cloud.storage.StorageManager; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.GuestOSCategoryDao; +import com.cloud.storage.dao.GuestOSDao; +import com.cloud.storage.dao.StoragePoolHostDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.AccountVO; +import com.cloud.user.User; +import com.cloud.user.UserVO; +import com.cloud.utils.Pair; +import com.cloud.utils.component.ComponentContext; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachineProfileImpl; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.VMInstanceDao; + +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.test.utils.SpringUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.filter.TypeFilter; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(loader = AnnotationConfigContextLoader.class) +public class ImplicitPlannerTest { + + @Inject + ImplicitDedicationPlanner planner = new ImplicitDedicationPlanner(); + @Inject + HostDao hostDao; + @Inject + DataCenterDao dcDao; + @Inject + HostPodDao podDao; + @Inject + ClusterDao clusterDao; + @Inject + GuestOSDao guestOSDao; + @Inject + GuestOSCategoryDao guestOSCategoryDao; + @Inject + DiskOfferingDao diskOfferingDao; + @Inject + StoragePoolHostDao poolHostDao; + @Inject + UserVmDao vmDao; + @Inject + VMInstanceDao vmInstanceDao; + @Inject + VolumeDao volsDao; + @Inject + CapacityManager capacityMgr; + @Inject + ConfigurationDao configDao; + @Inject + PrimaryDataStoreDao storagePoolDao; + @Inject + CapacityDao capacityDao; + @Inject + AccountManager accountMgr; + @Inject + StorageManager storageMgr; + @Inject + DataStoreManager dataStoreMgr; + @Inject + ClusterDetailsDao clusterDetailsDao; + @Inject + ServiceOfferingDao serviceOfferingDao; + @Inject + ServiceOfferingDetailsDao serviceOfferingDetailsDao; + @Inject + ResourceManager resourceMgr; + + private static long domainId = 5L; + long dataCenterId = 1L; + long accountId = 200L; + long offeringId = 12L; + int noOfCpusInOffering = 1; + int cpuSpeedInOffering = 500; + int ramInOffering = 512; + AccountVO acct = new AccountVO(accountId); + + @Before + public void setUp() { + ComponentContext.initComponentsLifeCycle(); + + acct.setType(Account.ACCOUNT_TYPE_NORMAL); + acct.setAccountName("user1"); + acct.setDomainId(domainId); + acct.setId(accountId); + + UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN); + CallContext.register(user, acct); + } + + @After + public void tearDown() { + CallContext.unregister(); + } + + @Test + public void checkWhenDcInAvoidList() throws InsufficientServerCapacityException { + DataCenterVO mockDc = mock(DataCenterVO.class); + ExcludeList avoids = mock(ExcludeList.class); + VirtualMachineProfileImpl vmProfile = mock(VirtualMachineProfileImpl.class); + VMInstanceVO vm = mock(VMInstanceVO.class); + DataCenterDeployment plan = mock(DataCenterDeployment.class); + + when(avoids.shouldAvoid(mockDc)).thenReturn(true); + when(vmProfile.getVirtualMachine()).thenReturn(vm); + when(vm.getDataCenterId()).thenReturn(1L); + when(dcDao.findById(1L)).thenReturn(mockDc); + + List clusterList = planner.orderClusters(vmProfile, plan, avoids); + assertTrue("Cluster list should be null/empty if the dc is in avoid list", (clusterList == null || clusterList.isEmpty())); + } + + @Test + public void checkStrictModeWithCurrentAccountVmsPresent() throws InsufficientServerCapacityException { + VirtualMachineProfileImpl vmProfile = mock(VirtualMachineProfileImpl.class); + DataCenterDeployment plan = mock(DataCenterDeployment.class); + ExcludeList avoids = new ExcludeList(); + + initializeForTest(vmProfile, plan); + + initializeForImplicitPlannerTest(false); + + List clusterList = planner.orderClusters(vmProfile, plan, avoids); + + // Validations. + // Check cluster 2 and 3 are not in the cluster list. + // Host 6 and 7 should also be in avoid list. + assertFalse("Cluster list should not be null/empty", (clusterList == null || clusterList.isEmpty())); + boolean foundNeededCluster = false; + for (Long cluster : clusterList) { + if (cluster != 1) { + fail("Found a cluster that shouldn't have been present, cluster id : " + cluster); + } else { + foundNeededCluster = true; + } + } + assertTrue("Didn't find cluster 1 in the list. It should have been present", foundNeededCluster); + + Set hostsInAvoidList = avoids.getHostsToAvoid(); + assertFalse("Host 5 shouldn't have be in the avoid list, but it is present", hostsInAvoidList.contains(5L)); + Set hostsThatShouldBeInAvoidList = new HashSet(); + hostsThatShouldBeInAvoidList.add(6L); + hostsThatShouldBeInAvoidList.add(7L); + assertTrue("Hosts 6 and 7 that should have been present were not found in avoid list", hostsInAvoidList.containsAll(hostsThatShouldBeInAvoidList)); + } + + @Test + public void checkStrictModeHostWithCurrentAccountVmsFull() throws InsufficientServerCapacityException { + @SuppressWarnings("unchecked") + VirtualMachineProfileImpl vmProfile = mock(VirtualMachineProfileImpl.class); + DataCenterDeployment plan = mock(DataCenterDeployment.class); + ExcludeList avoids = new ExcludeList(); + + initializeForTest(vmProfile, plan); + + initializeForImplicitPlannerTest(false); + + // Mark the host 5 with current account vms to be in avoid list. + avoids.addHost(5L); + List clusterList = planner.orderClusters(vmProfile, plan, avoids); + + // Validations. + // Check cluster 1 and 3 are not in the cluster list. + // Host 5 and 7 should also be in avoid list. + assertFalse("Cluster list should not be null/empty", (clusterList == null || clusterList.isEmpty())); + boolean foundNeededCluster = false; + for (Long cluster : clusterList) { + if (cluster != 2) { + fail("Found a cluster that shouldn't have been present, cluster id : " + cluster); + } else { + foundNeededCluster = true; + } + } + assertTrue("Didn't find cluster 2 in the list. It should have been present", foundNeededCluster); + + Set hostsInAvoidList = avoids.getHostsToAvoid(); + assertFalse("Host 6 shouldn't have be in the avoid list, but it is present", hostsInAvoidList.contains(6L)); + Set hostsThatShouldBeInAvoidList = new HashSet(); + hostsThatShouldBeInAvoidList.add(5L); + hostsThatShouldBeInAvoidList.add(7L); + assertTrue("Hosts 5 and 7 that should have been present were not found in avoid list", hostsInAvoidList.containsAll(hostsThatShouldBeInAvoidList)); + } + + @Test + public void checkStrictModeNoHostsAvailable() throws InsufficientServerCapacityException { + @SuppressWarnings("unchecked") + VirtualMachineProfileImpl vmProfile = mock(VirtualMachineProfileImpl.class); + DataCenterDeployment plan = mock(DataCenterDeployment.class); + ExcludeList avoids = new ExcludeList(); + + initializeForTest(vmProfile, plan); + + initializeForImplicitPlannerTest(false); + + // Mark the host 5 and 6 to be in avoid list. + avoids.addHost(5L); + avoids.addHost(6L); + List clusterList = planner.orderClusters(vmProfile, plan, avoids); + + // Validations. + // Check cluster list is empty. + assertTrue("Cluster list should not be null/empty", (clusterList == null || clusterList.isEmpty())); + } + + @Test + public void checkPreferredModePreferredHostAvailable() throws InsufficientServerCapacityException { + @SuppressWarnings("unchecked") + VirtualMachineProfileImpl vmProfile = mock(VirtualMachineProfileImpl.class); + DataCenterDeployment plan = mock(DataCenterDeployment.class); + ExcludeList avoids = new ExcludeList(); + + initializeForTest(vmProfile, plan); + + initializeForImplicitPlannerTest(true); + + // Mark the host 5 and 6 to be in avoid list. + avoids.addHost(5L); + avoids.addHost(6L); + List clusterList = planner.orderClusters(vmProfile, plan, avoids); + + // Validations. + // Check cluster 1 and 2 are not in the cluster list. + // Host 5 and 6 should also be in avoid list. + assertFalse("Cluster list should not be null/empty", (clusterList == null || clusterList.isEmpty())); + boolean foundNeededCluster = false; + for (Long cluster : clusterList) { + if (cluster != 3) { + fail("Found a cluster that shouldn't have been present, cluster id : " + cluster); + } else { + foundNeededCluster = true; + } + } + assertTrue("Didn't find cluster 3 in the list. It should have been present", foundNeededCluster); + + Set hostsInAvoidList = avoids.getHostsToAvoid(); + assertFalse("Host 7 shouldn't have be in the avoid list, but it is present", hostsInAvoidList.contains(7L)); + Set hostsThatShouldBeInAvoidList = new HashSet(); + hostsThatShouldBeInAvoidList.add(5L); + hostsThatShouldBeInAvoidList.add(6L); + assertTrue("Hosts 5 and 6 that should have been present were not found in avoid list", hostsInAvoidList.containsAll(hostsThatShouldBeInAvoidList)); + } + + @Test + public void checkPreferredModeNoHostsAvailable() throws InsufficientServerCapacityException { + @SuppressWarnings("unchecked") + VirtualMachineProfileImpl vmProfile = mock(VirtualMachineProfileImpl.class); + DataCenterDeployment plan = mock(DataCenterDeployment.class); + ExcludeList avoids = new ExcludeList(); + + initializeForTest(vmProfile, plan); + + initializeForImplicitPlannerTest(false); + + // Mark the host 5, 6 and 7 to be in avoid list. + avoids.addHost(5L); + avoids.addHost(6L); + avoids.addHost(7L); + List clusterList = planner.orderClusters(vmProfile, plan, avoids); + + // Validations. + // Check cluster list is empty. + assertTrue("Cluster list should not be null/empty", (clusterList == null || clusterList.isEmpty())); + } + + private void initializeForTest(VirtualMachineProfileImpl vmProfile, DataCenterDeployment plan) { + DataCenterVO mockDc = mock(DataCenterVO.class); + VMInstanceVO vm = mock(VMInstanceVO.class); + UserVmVO userVm = mock(UserVmVO.class); + ServiceOfferingVO offering = mock(ServiceOfferingVO.class); + + AccountVO account = mock(AccountVO.class); + when(account.getId()).thenReturn(accountId); + when(account.getAccountId()).thenReturn(accountId); + when(vmProfile.getOwner()).thenReturn(account); + when(vmProfile.getVirtualMachine()).thenReturn(vm); + when(vmProfile.getId()).thenReturn(12L); + when(vmDao.findById(12L)).thenReturn(userVm); + when(userVm.getAccountId()).thenReturn(accountId); + + when(vm.getDataCenterId()).thenReturn(dataCenterId); + when(dcDao.findById(1L)).thenReturn(mockDc); + when(plan.getDataCenterId()).thenReturn(dataCenterId); + when(plan.getClusterId()).thenReturn(null); + when(plan.getPodId()).thenReturn(null); + when(configDao.getValue(anyString())).thenReturn("false").thenReturn("CPU"); + + // Mock offering details. + when(vmProfile.getServiceOffering()).thenReturn(offering); + when(offering.getId()).thenReturn(offeringId); + when(vmProfile.getServiceOfferingId()).thenReturn(offeringId); + when(offering.getCpu()).thenReturn(noOfCpusInOffering); + when(offering.getSpeed()).thenReturn(cpuSpeedInOffering); + when(offering.getRamSize()).thenReturn(ramInOffering); + + List clustersWithEnoughCapacity = new ArrayList(); + clustersWithEnoughCapacity.add(1L); + clustersWithEnoughCapacity.add(2L); + clustersWithEnoughCapacity.add(3L); + when( + capacityDao.listClustersInZoneOrPodByHostCapacities(dataCenterId, noOfCpusInOffering * cpuSpeedInOffering, ramInOffering * 1024L * 1024L, + Capacity.CAPACITY_TYPE_CPU, true)).thenReturn(clustersWithEnoughCapacity); + + Map clusterCapacityMap = new HashMap(); + clusterCapacityMap.put(1L, 2048D); + clusterCapacityMap.put(2L, 2048D); + clusterCapacityMap.put(3L, 2048D); + Pair, Map> clustersOrderedByCapacity = new Pair, Map>(clustersWithEnoughCapacity, clusterCapacityMap); + when(capacityDao.orderClustersByAggregateCapacity(dataCenterId, Capacity.CAPACITY_TYPE_CPU, true)).thenReturn(clustersOrderedByCapacity); + + List disabledClusters = new ArrayList(); + List clustersWithDisabledPods = new ArrayList(); + when(clusterDao.listDisabledClusters(dataCenterId, null)).thenReturn(disabledClusters); + when(clusterDao.listClustersWithDisabledPods(dataCenterId)).thenReturn(clustersWithDisabledPods); + } + + private void initializeForImplicitPlannerTest(boolean preferred) { + String plannerMode = new String("Strict"); + if (preferred) { + plannerMode = new String("Preferred"); + } + + Map details = new HashMap(); + details.put("ImplicitDedicationMode", plannerMode); + when(serviceOfferingDetailsDao.listDetailsKeyPairs(offeringId)).thenReturn(details); + + // Initialize hosts in clusters + HostVO host1 = mock(HostVO.class); + when(host1.getId()).thenReturn(5L); + HostVO host2 = mock(HostVO.class); + when(host2.getId()).thenReturn(6L); + HostVO host3 = mock(HostVO.class); + when(host3.getId()).thenReturn(7L); + List hostsInCluster1 = new ArrayList(); + List hostsInCluster2 = new ArrayList(); + List hostsInCluster3 = new ArrayList(); + hostsInCluster1.add(host1); + hostsInCluster2.add(host2); + hostsInCluster3.add(host3); + when(resourceMgr.listAllHostsInCluster(1)).thenReturn(hostsInCluster1); + when(resourceMgr.listAllHostsInCluster(2)).thenReturn(hostsInCluster2); + when(resourceMgr.listAllHostsInCluster(3)).thenReturn(hostsInCluster3); + + // Mock vms on each host. + long offeringIdForVmsOfThisAccount = 15L; + long offeringIdForVmsOfOtherAccount = 16L; + UserVmVO vm1 = mock(UserVmVO.class); + when(vm1.getAccountId()).thenReturn(accountId); + when(vm1.getServiceOfferingId()).thenReturn(offeringIdForVmsOfThisAccount); + UserVmVO vm2 = mock(UserVmVO.class); + when(vm2.getAccountId()).thenReturn(accountId); + when(vm2.getServiceOfferingId()).thenReturn(offeringIdForVmsOfThisAccount); + // Vm from different account + UserVmVO vm3 = mock(UserVmVO.class); + when(vm3.getAccountId()).thenReturn(201L); + when(vm3.getServiceOfferingId()).thenReturn(offeringIdForVmsOfOtherAccount); + List vmsForHost1 = new ArrayList(); + List vmsForHost2 = new ArrayList(); + List vmsForHost3 = new ArrayList(); + List stoppedVmsForHost = new ArrayList(); + // Host 2 is empty. + vmsForHost1.add(vm1); + vmsForHost1.add(vm2); + vmsForHost3.add(vm3); + when(vmInstanceDao.listUpByHostId(5L)).thenReturn(vmsForHost1); + when(vmInstanceDao.listUpByHostId(6L)).thenReturn(vmsForHost2); + when(vmInstanceDao.listUpByHostId(7L)).thenReturn(vmsForHost3); + when(vmInstanceDao.listByLastHostId(5L)).thenReturn(stoppedVmsForHost); + when(vmInstanceDao.listByLastHostId(6L)).thenReturn(stoppedVmsForHost); + when(vmInstanceDao.listByLastHostId(7L)).thenReturn(stoppedVmsForHost); + + // Mock the offering with which the vm was created. + ServiceOfferingVO offeringForVmOfThisAccount = mock(ServiceOfferingVO.class); + when(serviceOfferingDao.findByIdIncludingRemoved(offeringIdForVmsOfThisAccount)).thenReturn(offeringForVmOfThisAccount); + when(offeringForVmOfThisAccount.getDeploymentPlanner()).thenReturn(planner.getName()); + + ServiceOfferingVO offeringForVMOfOtherAccount = mock(ServiceOfferingVO.class); + when(serviceOfferingDao.findByIdIncludingRemoved(offeringIdForVmsOfOtherAccount)).thenReturn(offeringForVMOfOtherAccount); + when(offeringForVMOfOtherAccount.getDeploymentPlanner()).thenReturn("FirstFitPlanner"); + } + + @Configuration + @ComponentScan(basePackageClasses = {ImplicitDedicationPlanner.class}, + includeFilters = {@Filter(value = TestConfiguration.Library.class, type = FilterType.CUSTOM)}, + useDefaultFilters = false) + public static class TestConfiguration extends SpringUtils.CloudStackTestConfiguration { + + @Bean + public HostDao hostDao() { + return Mockito.mock(HostDao.class); + } + + @Bean + public HostTagsDao hostTagsDao() { + return Mockito.mock(HostTagsDao.class); + } + + @Bean + public HostGpuGroupsDao hostGpuGroupsDao() { + return Mockito.mock(HostGpuGroupsDao.class); + } + + @Bean + public DataCenterDao dcDao() { + return Mockito.mock(DataCenterDao.class); + } + + @Bean + public HostPodDao hostPodDao() { + return Mockito.mock(HostPodDao.class); + } + + @Bean + public ClusterDao clusterDao() { + return Mockito.mock(ClusterDao.class); + } + + @Bean + public GuestOSDao guestOsDao() { + return Mockito.mock(GuestOSDao.class); + } + + @Bean + public GuestOSCategoryDao guestOsCategoryDao() { + return Mockito.mock(GuestOSCategoryDao.class); + } + + @Bean + public DiskOfferingDao diskOfferingDao() { + return Mockito.mock(DiskOfferingDao.class); + } + + @Bean + public StoragePoolHostDao storagePoolHostDao() { + return Mockito.mock(StoragePoolHostDao.class); + } + + @Bean + public UserVmDao userVmDao() { + return Mockito.mock(UserVmDao.class); + } + + @Bean + public VMInstanceDao vmInstanceDao() { + return Mockito.mock(VMInstanceDao.class); + } + + @Bean + public VolumeDao volumeDao() { + return Mockito.mock(VolumeDao.class); + } + + @Bean + public CapacityManager capacityManager() { + return Mockito.mock(CapacityManager.class); + } + + @Bean + public ConfigurationDao configurationDao() { + return Mockito.mock(ConfigurationDao.class); + } + + @Bean + public PrimaryDataStoreDao primaryDataStoreDao() { + return Mockito.mock(PrimaryDataStoreDao.class); + } + + @Bean + public CapacityDao capacityDao() { + return Mockito.mock(CapacityDao.class); + } + + @Bean + public AccountManager accountManager() { + return Mockito.mock(AccountManager.class); + } + + @Bean + public StorageManager storageManager() { + return Mockito.mock(StorageManager.class); + } + + @Bean + public DataStoreManager dataStoreManager() { + return Mockito.mock(DataStoreManager.class); + } + + @Bean + public ClusterDetailsDao clusterDetailsDao() { + return Mockito.mock(ClusterDetailsDao.class); + } + + @Bean + public ServiceOfferingDao serviceOfferingDao() { + return Mockito.mock(ServiceOfferingDao.class); + } + + @Bean + public ServiceOfferingDetailsDao serviceOfferingDetailsDao() { + return Mockito.mock(ServiceOfferingDetailsDao.class); + } + + @Bean + public ResourceManager resourceManager() { + return Mockito.mock(ResourceManager.class); + } + + public static class Library implements TypeFilter { + @Override + public boolean match(MetadataReader mdr, MetadataReaderFactory arg1) throws IOException { + ComponentScan cs = TestConfiguration.class.getAnnotation(ComponentScan.class); + return SpringUtils.includedInBasePackageClasses(mdr.getClassMetadata().getClassName(), cs); + } + } + } +} diff --git a/cosmic-core/plugins/deployment-planners/user-concentrated-pod/pom.xml b/cosmic-core/plugins/deployment-planners/user-concentrated-pod/pom.xml new file mode 100644 index 0000000000..bda7cf313e --- /dev/null +++ b/cosmic-core/plugins/deployment-planners/user-concentrated-pod/pom.xml @@ -0,0 +1,11 @@ + + 4.0.0 + cloud-plugin-planner-user-concentrated-pod + Cosmic Plugin - User Concentrated Pod Deployment Planner + + cloud.cosmic + cosmic-plugins + 5.1.0.0-SNAPSHOT + ../../pom.xml + + \ No newline at end of file diff --git a/cosmic-core/plugins/deployment-planners/user-concentrated-pod/src/main/java/com/cloud/deploy/UserConcentratedPodPlanner.java b/cosmic-core/plugins/deployment-planners/user-concentrated-pod/src/main/java/com/cloud/deploy/UserConcentratedPodPlanner.java new file mode 100644 index 0000000000..f260538c8c --- /dev/null +++ b/cosmic-core/plugins/deployment-planners/user-concentrated-pod/src/main/java/com/cloud/deploy/UserConcentratedPodPlanner.java @@ -0,0 +1,141 @@ +// 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.deploy; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.cloud.utils.Pair; +import com.cloud.vm.VirtualMachineProfile; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class UserConcentratedPodPlanner extends FirstFitPlanner implements DeploymentClusterPlanner { + + private static final Logger s_logger = LoggerFactory.getLogger(UserConcentratedPodPlanner.class); + + /** + * This method should reorder the given list of Cluster Ids by applying any necessary heuristic + * for this planner + * For UserConcentratedPodPlanner we need to order the clusters in a zone across pods, by considering those pods first which have more number of VMs for this account + * This reordering is not done incase the clusters within single pod are passed when the allocation is applied at pod-level. + * @return List ordered list of Cluster Ids + */ + @Override + protected List reorderClusters(long id, boolean isZone, Pair, Map> clusterCapacityInfo, VirtualMachineProfile vmProfile, + DeploymentPlan plan) { + List clusterIdsByCapacity = clusterCapacityInfo.first(); + if (vmProfile.getOwner() == null || !isZone) { + return clusterIdsByCapacity; + } + return applyUserConcentrationPodHeuristicToClusters(id, clusterIdsByCapacity, vmProfile.getOwner().getAccountId()); + } + + private List applyUserConcentrationPodHeuristicToClusters(long zoneId, List prioritizedClusterIds, long accountId) { + //user has VMs in certain pods. - prioritize those pods first + //UserConcentratedPod strategy + List clusterList = new ArrayList(); + List podIds = listPodsByUserConcentration(zoneId, accountId); + if (!podIds.isEmpty()) { + clusterList = reorderClustersByPods(prioritizedClusterIds, podIds); + } else { + clusterList = prioritizedClusterIds; + } + return clusterList; + } + + private List reorderClustersByPods(List clusterIds, List podIds) { + + if (s_logger.isDebugEnabled()) { + s_logger.debug("Reordering cluster list as per pods ordered by user concentration"); + } + + Map> podClusterMap = clusterDao.getPodClusterIdMap(clusterIds); + + if (s_logger.isTraceEnabled()) { + s_logger.trace("Pod To cluster Map is: " + podClusterMap); + } + + List reorderedClusters = new ArrayList(); + for (Long pod : podIds) { + if (podClusterMap.containsKey(pod)) { + List clustersOfThisPod = podClusterMap.get(pod); + if (clustersOfThisPod != null) { + for (Long clusterId : clusterIds) { + if (clustersOfThisPod.contains(clusterId)) { + reorderedClusters.add(clusterId); + } + } + clusterIds.removeAll(clustersOfThisPod); + } + } + } + reorderedClusters.addAll(clusterIds); + + if (s_logger.isTraceEnabled()) { + s_logger.trace("Reordered cluster list: " + reorderedClusters); + } + return reorderedClusters; + } + + protected List listPodsByUserConcentration(long zoneId, long accountId) { + + if (s_logger.isDebugEnabled()) { + s_logger.debug("Applying UserConcentratedPod heuristic for account: " + accountId); + } + + List prioritizedPods = vmDao.listPodIdsHavingVmsforAccount(zoneId, accountId); + + if (s_logger.isTraceEnabled()) { + s_logger.trace("List of pods to be considered, after applying UserConcentratedPod heuristic: " + prioritizedPods); + } + + return prioritizedPods; + } + + /** + * This method should reorder the given list of Pod Ids by applying any necessary heuristic + * for this planner + * For UserConcentratedPodPlanner we need to order the pods by considering those pods first which have more number of VMs for this account + * @return List ordered list of Pod Ids + */ + @Override + protected List reorderPods(Pair, Map> podCapacityInfo, VirtualMachineProfile vmProfile, DeploymentPlan plan) { + List podIdsByCapacity = podCapacityInfo.first(); + if (vmProfile.getOwner() == null) { + return podIdsByCapacity; + } + long accountId = vmProfile.getOwner().getAccountId(); + + //user has VMs in certain pods. - prioritize those pods first + //UserConcentratedPod strategy + List podIds = listPodsByUserConcentration(plan.getDataCenterId(), accountId); + if (!podIds.isEmpty()) { + //remove pods that dont have capacity for this vm + podIds.retainAll(podIdsByCapacity); + podIdsByCapacity.removeAll(podIds); + podIds.addAll(podIdsByCapacity); + return podIds; + } else { + return podIdsByCapacity; + } + + } + +} diff --git a/cosmic-core/plugins/deployment-planners/user-concentrated-pod/src/main/resources/META-INF/cloudstack/user-concentrated-pod/module.properties b/cosmic-core/plugins/deployment-planners/user-concentrated-pod/src/main/resources/META-INF/cloudstack/user-concentrated-pod/module.properties new file mode 100644 index 0000000000..7a430b2822 --- /dev/null +++ b/cosmic-core/plugins/deployment-planners/user-concentrated-pod/src/main/resources/META-INF/cloudstack/user-concentrated-pod/module.properties @@ -0,0 +1,18 @@ +# 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. +name=user-concentrated-pod +parent=planner \ No newline at end of file diff --git a/cosmic-core/plugins/deployment-planners/user-concentrated-pod/src/main/resources/META-INF/cloudstack/user-concentrated-pod/spring-user-concentrated-pod-context.xml b/cosmic-core/plugins/deployment-planners/user-concentrated-pod/src/main/resources/META-INF/cloudstack/user-concentrated-pod/spring-user-concentrated-pod-context.xml new file mode 100644 index 0000000000..e26cb2be57 --- /dev/null +++ b/cosmic-core/plugins/deployment-planners/user-concentrated-pod/src/main/resources/META-INF/cloudstack/user-concentrated-pod/spring-user-concentrated-pod-context.xml @@ -0,0 +1,35 @@ + + + + + + + + + diff --git a/cosmic-core/plugins/deployment-planners/user-dispersing/pom.xml b/cosmic-core/plugins/deployment-planners/user-dispersing/pom.xml new file mode 100644 index 0000000000..a056f00e82 --- /dev/null +++ b/cosmic-core/plugins/deployment-planners/user-dispersing/pom.xml @@ -0,0 +1,11 @@ + + 4.0.0 + cloud-plugin-planner-user-dispersing + Cosmic Plugin - User Dispersing Deployment Planner + + cloud.cosmic + cosmic-plugins + 5.1.0.0-SNAPSHOT + ../../pom.xml + + \ No newline at end of file diff --git a/cosmic-core/plugins/deployment-planners/user-dispersing/src/main/java/com/cloud/deploy/UserDispersingPlanner.java b/cosmic-core/plugins/deployment-planners/user-dispersing/src/main/java/com/cloud/deploy/UserDispersingPlanner.java new file mode 100644 index 0000000000..0b2a895a36 --- /dev/null +++ b/cosmic-core/plugins/deployment-planners/user-dispersing/src/main/java/com/cloud/deploy/UserDispersingPlanner.java @@ -0,0 +1,200 @@ +// 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.deploy; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +import javax.naming.ConfigurationException; + +import com.cloud.configuration.Config; +import com.cloud.utils.NumbersUtil; +import com.cloud.utils.Pair; +import com.cloud.vm.VirtualMachineProfile; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class UserDispersingPlanner extends FirstFitPlanner implements DeploymentClusterPlanner { + + private static final Logger s_logger = LoggerFactory.getLogger(UserDispersingPlanner.class); + + /** + * This method should reorder the given list of Cluster Ids by applying any necessary heuristic + * for this planner + * For UserDispersingPlanner we need to order the clusters by considering the number of VMs for this account + * @return List ordered list of Cluster Ids + */ + @Override + protected List reorderClusters(long id, boolean isZone, Pair, Map> clusterCapacityInfo, VirtualMachineProfile vmProfile, + DeploymentPlan plan) { + List clusterIdsByCapacity = clusterCapacityInfo.first(); + if (vmProfile.getOwner() == null) { + return clusterIdsByCapacity; + } + long accountId = vmProfile.getOwner().getAccountId(); + Pair, Map> clusterIdsVmCountInfo = listClustersByUserDispersion(id, isZone, accountId); + + //now we have 2 cluster lists - one ordered by capacity and the other by number of VMs for this account + //need to apply weights to these to find the correct ordering to follow + + if (_userDispersionWeight == 1.0f) { + List clusterIds = clusterIdsVmCountInfo.first(); + clusterIds.retainAll(clusterIdsByCapacity); + return clusterIds; + } else { + //apply weights to the two lists + return orderByApplyingWeights(clusterCapacityInfo, clusterIdsVmCountInfo, accountId); + } + + } + + /** + * This method should reorder the given list of Pod Ids by applying any necessary heuristic + * for this planner + * For UserDispersingPlanner we need to order the pods by considering the number of VMs for this account + * @return List ordered list of Pod Ids + */ + @Override + protected List reorderPods(Pair, Map> podCapacityInfo, VirtualMachineProfile vmProfile, DeploymentPlan plan) { + List podIdsByCapacity = podCapacityInfo.first(); + if (vmProfile.getOwner() == null) { + return podIdsByCapacity; + } + long accountId = vmProfile.getOwner().getAccountId(); + + Pair, Map> podIdsVmCountInfo = listPodsByUserDispersion(plan.getDataCenterId(), accountId); + + //now we have 2 pod lists - one ordered by capacity and the other by number of VMs for this account + //need to apply weights to these to find the correct ordering to follow + + if (_userDispersionWeight == 1.0f) { + List podIds = podIdsVmCountInfo.first(); + podIds.retainAll(podIdsByCapacity); + return podIds; + } else { + //apply weights to the two lists + return orderByApplyingWeights(podCapacityInfo, podIdsVmCountInfo, accountId); + } + + } + + protected Pair, Map> listClustersByUserDispersion(long id, boolean isZone, long accountId) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Applying Userdispersion heuristic to clusters for account: " + accountId); + } + Pair, Map> clusterIdsVmCountInfo; + if (isZone) { + clusterIdsVmCountInfo = vmInstanceDao.listClusterIdsInZoneByVmCount(id, accountId); + } else { + clusterIdsVmCountInfo = vmInstanceDao.listClusterIdsInPodByVmCount(id, accountId); + } + if (s_logger.isTraceEnabled()) { + s_logger.trace("List of clusters in ascending order of number of VMs: " + clusterIdsVmCountInfo.first()); + } + return clusterIdsVmCountInfo; + } + + protected Pair, Map> listPodsByUserDispersion(long dataCenterId, long accountId) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Applying Userdispersion heuristic to pods for account: " + accountId); + } + Pair, Map> podIdsVmCountInfo = vmInstanceDao.listPodIdsInZoneByVmCount(dataCenterId, accountId); + if (s_logger.isTraceEnabled()) { + s_logger.trace("List of pods in ascending order of number of VMs: " + podIdsVmCountInfo.first()); + } + + return podIdsVmCountInfo; + } + + private List orderByApplyingWeights(Pair, Map> capacityInfo, Pair, Map> vmCountInfo, long accountId) { + List capacityOrderedIds = capacityInfo.first(); + List vmCountOrderedIds = vmCountInfo.first(); + Map capacityMap = capacityInfo.second(); + Map vmCountMap = vmCountInfo.second(); + + if (s_logger.isTraceEnabled()) { + s_logger.trace("Capacity Id list: " + capacityOrderedIds + " , capacityMap:" + capacityMap); + } + if (s_logger.isTraceEnabled()) { + s_logger.trace("Vm Count Id list: " + vmCountOrderedIds + " , vmCountMap:" + vmCountMap); + } + + List idsReorderedByWeights = new ArrayList(); + float capacityWeight = (1.0f - _userDispersionWeight); + + if (s_logger.isDebugEnabled()) { + s_logger.debug("Applying userDispersionWeight: " + _userDispersionWeight); + } + //normalize the vmCountMap + LinkedHashMap normalisedVmCountIdMap = new LinkedHashMap(); + + Long totalVmsOfAccount = vmInstanceDao.countRunningByAccount(accountId); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Total VMs for account: " + totalVmsOfAccount); + } + for (Long id : vmCountOrderedIds) { + Double normalisedCount = vmCountMap.get(id) / totalVmsOfAccount; + normalisedVmCountIdMap.put(id, normalisedCount); + } + + //consider only those ids that are in capacity map. + + SortedMap> sortedMap = new TreeMap>(); + for (Long id : capacityOrderedIds) { + Double weightedCapacityValue = capacityMap.get(id) * capacityWeight; + Double weightedVmCountValue = normalisedVmCountIdMap.get(id) * _userDispersionWeight; + Double totalWeight = weightedCapacityValue + weightedVmCountValue; + if (sortedMap.containsKey(totalWeight)) { + List idList = sortedMap.get(totalWeight); + idList.add(id); + sortedMap.put(totalWeight, idList); + } else { + List idList = new ArrayList(); + idList.add(id); + sortedMap.put(totalWeight, idList); + } + } + + for (List idList : sortedMap.values()) { + idsReorderedByWeights.addAll(idList); + } + + if (s_logger.isTraceEnabled()) { + s_logger.trace("Reordered Id list: " + idsReorderedByWeights); + } + + return idsReorderedByWeights; + } + + float _userDispersionWeight; + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + super.configure(name, params); + + String weight = configDao.getValue(Config.VmUserDispersionWeight.key()); + _userDispersionWeight = NumbersUtil.parseFloat(weight, 1.0f); + + return true; + } + +} diff --git a/cosmic-core/plugins/ha-planners/skip-heurestics/pom.xml b/cosmic-core/plugins/ha-planners/skip-heurestics/pom.xml new file mode 100644 index 0000000000..f4bfe1acfd --- /dev/null +++ b/cosmic-core/plugins/ha-planners/skip-heurestics/pom.xml @@ -0,0 +1,11 @@ + + 4.0.0 + cloud-plugin-planner-skip-heurestics + Cosmic Plugin - Skip Heurestics Planner + + cloud.cosmic + cosmic-plugins + 5.1.0.0-SNAPSHOT + ../../pom.xml + + \ No newline at end of file diff --git a/cosmic-core/plugins/ha-planners/skip-heurestics/src/main/java/com/cloud/deploy/SkipHeuresticsPlanner.java b/cosmic-core/plugins/ha-planners/skip-heurestics/src/main/java/com/cloud/deploy/SkipHeuresticsPlanner.java new file mode 100644 index 0000000000..a0991dae4f --- /dev/null +++ b/cosmic-core/plugins/ha-planners/skip-heurestics/src/main/java/com/cloud/deploy/SkipHeuresticsPlanner.java @@ -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. +package com.cloud.deploy; + +import java.util.List; +import java.util.Map; + +import javax.naming.ConfigurationException; + +import com.cloud.vm.VirtualMachineProfile; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SkipHeuresticsPlanner extends FirstFitPlanner implements HAPlanner { + private static final Logger s_logger = LoggerFactory.getLogger(SkipHeuresticsPlanner.class); + + + /** + * This method should remove the clusters crossing capacity threshold + * to avoid further vm allocation on it. + * + * In case of HA, we shouldn't consider this threshold as we have reserved the capacity for such emergencies. + */ + @Override + protected void removeClustersCrossingThreshold(List clusterListForVmAllocation, ExcludeList avoid, + VirtualMachineProfile vmProfile, DeploymentPlan plan){ + if (s_logger.isDebugEnabled()) { + s_logger.debug("Deploying vm during HA process, so skipping disable threshold check"); + } + return; + } + + @Override + public boolean canHandle(VirtualMachineProfile vm, DeploymentPlan plan, ExcludeList avoid) { + return true; + } + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + super.configure(name, params); + + return true; + + } + +} \ No newline at end of file diff --git a/cosmic-core/plugins/ha-planners/skip-heurestics/src/main/resources/META-INF/cloudstack/skip-heurestics/module.properties b/cosmic-core/plugins/ha-planners/skip-heurestics/src/main/resources/META-INF/cloudstack/skip-heurestics/module.properties new file mode 100644 index 0000000000..dfe0641ba4 --- /dev/null +++ b/cosmic-core/plugins/ha-planners/skip-heurestics/src/main/resources/META-INF/cloudstack/skip-heurestics/module.properties @@ -0,0 +1,18 @@ +# 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. +name=skip-heurestics +parent=planner diff --git a/cosmic-core/plugins/ha-planners/skip-heurestics/src/main/resources/META-INF/cloudstack/skip-heurestics/spring-skip-heurestics-context.xml b/cosmic-core/plugins/ha-planners/skip-heurestics/src/main/resources/META-INF/cloudstack/skip-heurestics/spring-skip-heurestics-context.xml new file mode 100644 index 0000000000..93a015874a --- /dev/null +++ b/cosmic-core/plugins/ha-planners/skip-heurestics/src/main/resources/META-INF/cloudstack/skip-heurestics/spring-skip-heurestics-context.xml @@ -0,0 +1,26 @@ + + + + + + + + + + diff --git a/cosmic-core/plugins/host-allocators/random/pom.xml b/cosmic-core/plugins/host-allocators/random/pom.xml new file mode 100644 index 0000000000..e646432b0b --- /dev/null +++ b/cosmic-core/plugins/host-allocators/random/pom.xml @@ -0,0 +1,11 @@ + + 4.0.0 + cloud-plugin-host-allocator-random + Cosmic Plugin - Host Allocator Random + + cloud.cosmic + cosmic-plugins + 5.1.0.0-SNAPSHOT + ../../pom.xml + + \ No newline at end of file diff --git a/cosmic-core/plugins/host-allocators/random/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java b/cosmic-core/plugins/host-allocators/random/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java new file mode 100644 index 0000000000..6686a69d9e --- /dev/null +++ b/cosmic-core/plugins/host-allocators/random/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java @@ -0,0 +1,171 @@ +// 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.agent.manager.allocator.impl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.inject.Inject; + +import com.cloud.agent.manager.allocator.HostAllocator; +import com.cloud.deploy.DeploymentPlan; +import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.host.Host; +import com.cloud.host.Host.Type; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.offering.ServiceOffering; +import com.cloud.resource.ResourceManager; +import com.cloud.utils.component.AdapterBase; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineProfile; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +@Component +public class RandomAllocator extends AdapterBase implements HostAllocator { + private static final Logger s_logger = LoggerFactory.getLogger(RandomAllocator.class); + @Inject + private HostDao _hostDao; + @Inject + private ResourceManager _resourceMgr; + + @Override + public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, int returnUpTo) { + return allocateTo(vmProfile, plan, type, avoid, returnUpTo, true); + } + + @Override + public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, List hosts, int returnUpTo, + boolean considerReservedCapacity) { + long dcId = plan.getDataCenterId(); + Long podId = plan.getPodId(); + Long clusterId = plan.getClusterId(); + ServiceOffering offering = vmProfile.getServiceOffering(); + List suitableHosts = new ArrayList(); + List hostsCopy = new ArrayList(hosts); + + if (type == Host.Type.Storage) { + return suitableHosts; + } + + String hostTag = offering.getHostTag(); + if (hostTag != null) { + s_logger.debug("Looking for hosts in dc: " + dcId + " pod:" + podId + " cluster:" + clusterId + " having host tag:" + hostTag); + } else { + s_logger.debug("Looking for hosts in dc: " + dcId + " pod:" + podId + " cluster:" + clusterId); + } + + // list all computing hosts, regardless of whether they support routing...it's random after all + if (hostTag != null) { + hostsCopy.retainAll(_hostDao.listByHostTag(type, clusterId, podId, dcId, hostTag)); + } else { + hostsCopy.retainAll(_resourceMgr.listAllUpAndEnabledHosts(type, clusterId, podId, dcId)); + } + + s_logger.debug("Random Allocator found " + hostsCopy.size() + " hosts"); + if (hostsCopy.size() == 0) { + return suitableHosts; + } + + Collections.shuffle(hostsCopy); + for (Host host : hostsCopy) { + if (suitableHosts.size() == returnUpTo) { + break; + } + + if (!avoid.shouldAvoid(host)) { + suitableHosts.add(host); + } else { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Host name: " + host.getName() + ", hostId: " + host.getId() + " is in avoid set, " + "skipping this and trying other available hosts"); + } + } + } + + if (s_logger.isDebugEnabled()) { + s_logger.debug("Random Host Allocator returning " + suitableHosts.size() + " suitable hosts"); + } + + return suitableHosts; + } + + @Override + public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, int returnUpTo, boolean considerReservedCapacity) { + + long dcId = plan.getDataCenterId(); + Long podId = plan.getPodId(); + Long clusterId = plan.getClusterId(); + ServiceOffering offering = vmProfile.getServiceOffering(); + + List suitableHosts = new ArrayList(); + + if (type == Host.Type.Storage) { + return suitableHosts; + } + + String hostTag = offering.getHostTag(); + if (hostTag != null) { + s_logger.debug("Looking for hosts in dc: " + dcId + " pod:" + podId + " cluster:" + clusterId + " having host tag:" + hostTag); + } else { + s_logger.debug("Looking for hosts in dc: " + dcId + " pod:" + podId + " cluster:" + clusterId); + } + + // list all computing hosts, regardless of whether they support routing...it's random after all + List hosts = new ArrayList(); + if (hostTag != null) { + hosts = _hostDao.listByHostTag(type, clusterId, podId, dcId, hostTag); + } else { + hosts = _resourceMgr.listAllUpAndEnabledHosts(type, clusterId, podId, dcId); + } + + s_logger.debug("Random Allocator found " + hosts.size() + " hosts"); + + if (hosts.size() == 0) { + return suitableHosts; + } + + Collections.shuffle(hosts); + for (Host host : hosts) { + if (suitableHosts.size() == returnUpTo) { + break; + } + + if (!avoid.shouldAvoid(host)) { + suitableHosts.add(host); + } else { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Host name: " + host.getName() + ", hostId: " + host.getId() + " is in avoid set, skipping this and trying other available hosts"); + } + } + } + if (s_logger.isDebugEnabled()) { + s_logger.debug("Random Host Allocator returning " + suitableHosts.size() + " suitable hosts"); + } + return suitableHosts; + } + + @Override + public boolean isVirtualMachineUpgradable(VirtualMachine vm, ServiceOffering offering) { + // currently we do no special checks to rule out a VM being upgradable to an offering, so + // return true + return true; + } +} diff --git a/cosmic-core/plugins/host-allocators/random/src/main/resources/META-INF/cloudstack/host-allocator-random/module.properties b/cosmic-core/plugins/host-allocators/random/src/main/resources/META-INF/cloudstack/host-allocator-random/module.properties new file mode 100644 index 0000000000..9a04174ec7 --- /dev/null +++ b/cosmic-core/plugins/host-allocators/random/src/main/resources/META-INF/cloudstack/host-allocator-random/module.properties @@ -0,0 +1,18 @@ +# 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. +name=host-allocator-random +parent=allocator \ No newline at end of file diff --git a/cosmic-core/plugins/host-allocators/random/src/main/resources/META-INF/cloudstack/host-allocator-random/spring-host-allocator-random-context.xml b/cosmic-core/plugins/host-allocators/random/src/main/resources/META-INF/cloudstack/host-allocator-random/spring-host-allocator-random-context.xml new file mode 100644 index 0000000000..8df1bdb97c --- /dev/null +++ b/cosmic-core/plugins/host-allocators/random/src/main/resources/META-INF/cloudstack/host-allocator-random/spring-host-allocator-random-context.xml @@ -0,0 +1,34 @@ + + + + + + + diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/database/BaremetalDhcpDaoImpl.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/database/BaremetalDhcpDaoImpl.java new file mode 100644 index 0000000000..19540fb47f --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/database/BaremetalDhcpDaoImpl.java @@ -0,0 +1,33 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +package com.cloud.baremetal.database; + +import com.cloud.utils.db.DB; +import com.cloud.utils.db.GenericDaoBase; + +import org.springframework.stereotype.Component; + +@Component +@DB() +public class BaremetalDhcpDaoImpl extends GenericDaoBase implements BaremetalDhcpDao { + + public BaremetalDhcpDaoImpl() { + } + +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/database/BaremetalPxeDaoImpl.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/database/BaremetalPxeDaoImpl.java new file mode 100644 index 0000000000..fd125eac21 --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/database/BaremetalPxeDaoImpl.java @@ -0,0 +1,29 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +package com.cloud.baremetal.database; + +import com.cloud.utils.db.DB; +import com.cloud.utils.db.GenericDaoBase; + +import org.springframework.stereotype.Component; + +@Component +@DB() +public class BaremetalPxeDaoImpl extends GenericDaoBase implements BaremetalPxeDao { +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/database/BaremetalRctVO.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/database/BaremetalRctVO.java new file mode 100644 index 0000000000..cab78acd8e --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/database/BaremetalRctVO.java @@ -0,0 +1,83 @@ +// 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.baremetal.database; + +import java.util.UUID; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +/** + * Created by frank on 5/8/14. + */ +@Entity +@Table(name = "baremetal_rct") +public class BaremetalRctVO implements InternalIdentity, Identity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "uuid") + private String uuid = UUID.randomUUID().toString(); + + @Column(name = "url") + private String url; + + @Column(name = "rct") + private String rct; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getRct() { + return rct; + } + + public void setRct(String rct) { + this.rct = rct; + } +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalDiscoverer.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalDiscoverer.java new file mode 100644 index 0000000000..f67c241bcf --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalDiscoverer.java @@ -0,0 +1,282 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +// Apache License, Version 2.0 (the "License"); you may not use this +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// Automatically generated by addcopyright.py at 04/03/2012 +package com.cloud.baremetal.manager; + +import java.net.InetAddress; +import java.net.URI; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import com.cloud.agent.api.StartupCommand; +import com.cloud.agent.api.StartupRoutingCommand; +import com.cloud.baremetal.networkservice.BareMetalResourceBase; +import com.cloud.configuration.Config; +import com.cloud.dc.ClusterVO; +import com.cloud.dc.DataCenterVO; +import com.cloud.exception.DiscoveryException; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.network.Network; +import com.cloud.resource.Discoverer; +import com.cloud.resource.DiscovererBase; +import com.cloud.resource.ResourceStateAdapter; +import com.cloud.resource.ServerResource; +import com.cloud.resource.UnableDeleteHostException; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.script.Script; +import com.cloud.utils.script.Script2; +import com.cloud.utils.script.Script2.ParamType; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.dao.VMInstanceDao; + +import org.apache.cloudstack.api.ApiConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BareMetalDiscoverer extends DiscovererBase implements Discoverer, ResourceStateAdapter { + protected static final Logger s_logger = LoggerFactory.getLogger(BareMetalDiscoverer.class); + @Inject + protected VMInstanceDao _vmDao = null; + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + _resourceMgr.registerResourceStateAdapter(this.getClass().getSimpleName(), this); + return super.configure(name, params); + } + + @Override + public boolean stop() { + _resourceMgr.unregisterResourceStateAdapter(this.getClass().getSimpleName()); + return super.stop(); + } + + @Override + public Map> find(long dcId, Long podId, Long clusterId, URI url, String username, String password, List hostTags) + throws DiscoveryException { + + /* Enable this after we decide to use addBaremetalHostCmd instead of addHostCmd + String discoverName = _params.get(ApiConstants.BAREMETAL_DISCOVER_NAME); + if (!this.getClass().getName().equals(discoverName)) { + return null; + } */ + + Map> resources = new HashMap>(); + Map details = new HashMap(); + + if (!url.getScheme().equals("http")) { + String msg = "urlString is not http so we're not taking care of the discovery for this: " + url; + s_logger.debug(msg); + return null; + } + if (clusterId == null) { + String msg = "must specify cluster Id when add host"; + s_logger.debug(msg); + throw new RuntimeException(msg); + } + + if (podId == null) { + String msg = "must specify pod Id when add host"; + s_logger.debug(msg); + throw new RuntimeException(msg); + } + + ClusterVO cluster = _clusterDao.findById(clusterId); + if (cluster == null || (cluster.getHypervisorType() != HypervisorType.BareMetal)) { + if (s_logger.isInfoEnabled()) + s_logger.info("invalid cluster id or cluster is not for Bare Metal hosts"); + return null; + } + + DataCenterVO zone = _dcDao.findById(dcId); + if (zone == null) { + throw new RuntimeException("Cannot find zone " + dcId); + } + + try { + String hostname = url.getHost(); + InetAddress ia = InetAddress.getByName(hostname); + String ipmiIp = ia.getHostAddress(); + String guid = UUID.nameUUIDFromBytes(ipmiIp.getBytes()).toString(); + + String injectScript = "scripts/util/ipmi.py"; + String scriptPath = Script.findScript("", injectScript); + if (scriptPath == null) { + throw new CloudRuntimeException("Unable to find key ipmi script " + + injectScript); + } + + final Script2 command = new Script2(scriptPath, s_logger); + command.add("ping"); + command.add("hostname="+ipmiIp); + command.add("usrname="+username); + command.add("password="+password, ParamType.PASSWORD); + final String result = command.execute(); + if (result != null) { + s_logger.warn(String.format("Can not set up ipmi connection(ip=%1$s, username=%2$s, password=%3$s, args) because %4$s", ipmiIp, username, "******", result)); + return null; + } + + ClusterVO clu = _clusterDao.findById(clusterId); + if (clu.getGuid() == null) { + clu.setGuid(UUID.randomUUID().toString()); + _clusterDao.update(clusterId, clu); + } + + Map params = new HashMap(); + params.putAll(_params); + params.put("zone", Long.toString(dcId)); + params.put("pod", Long.toString(podId)); + params.put("cluster", Long.toString(clusterId)); + params.put("guid", guid); + params.put(ApiConstants.PRIVATE_IP, ipmiIp); + params.put(ApiConstants.USERNAME, username); + params.put(ApiConstants.PASSWORD, password); + params.put("vmDao", _vmDao); + params.put("configDao", _configDao); + + String resourceClassName = _configDao.getValue(Config.ExternalBaremetalResourceClassName.key()); + BareMetalResourceBase resource = null; + if (resourceClassName != null) { + Class clazz = Class.forName(resourceClassName); + resource = (BareMetalResourceBase) clazz.newInstance(); + String externalUrl = _configDao.getValue(Config.ExternalBaremetalSystemUrl.key()); + if (externalUrl == null) { + throw new IllegalArgumentException(String.format("You must specify ExternalBaremetalSystemUrl in global config page as ExternalBaremetalResourceClassName is not null")); + } + details.put(BaremetalManager.ExternalBaremetalSystemUrl, externalUrl); + } else { + resource = new BareMetalResourceBase(); + } + resource.configure("Bare Metal Agent", params); + + String memCapacity = (String)params.get(ApiConstants.MEMORY); + String cpuCapacity = (String)params.get(ApiConstants.CPU_SPEED); + String cpuNum = (String)params.get(ApiConstants.CPU_NUMBER); + String mac = (String)params.get(ApiConstants.HOST_MAC); + if (hostTags != null && hostTags.size() != 0) { + details.put("hostTag", hostTags.get(0)); + } + details.put(ApiConstants.MEMORY, memCapacity); + details.put(ApiConstants.CPU_SPEED, cpuCapacity); + details.put(ApiConstants.CPU_NUMBER, cpuNum); + details.put(ApiConstants.HOST_MAC, mac); + details.put(ApiConstants.USERNAME, username); + details.put(ApiConstants.PASSWORD, password); + details.put(ApiConstants.PRIVATE_IP, ipmiIp); + String vmIp = (String)params.get(ApiConstants.IP_ADDRESS); + if (vmIp != null) { + details.put(ApiConstants.IP_ADDRESS, vmIp); + } + String isEchoScAgent = _configDao.getValue(Config.EnableBaremetalSecurityGroupAgentEcho.key()); + details.put(BaremetalManager.EchoSecurityGroupAgent, isEchoScAgent); + + resources.put(resource, details); + resource.start(); + + zone.setGatewayProvider(Network.Provider.ExternalGateWay.getName()); + zone.setDnsProvider(Network.Provider.ExternalDhcpServer.getName()); + zone.setDhcpProvider(Network.Provider.ExternalDhcpServer.getName()); + _dcDao.update(zone.getId(), zone); + + s_logger.debug(String.format("Discover Bare Metal host successfully(ip=%1$s, username=%2$s, password=%3%s," + + "cpuNum=%4$s, cpuCapacity-%5$s, memCapacity=%6$s)", ipmiIp, username, "******", cpuNum, cpuCapacity, memCapacity)); + return resources; + } catch (Exception e) { + s_logger.warn("Can not set up bare metal agent", e); + } + + return null; + } + + @Override + public void postDiscovery(List hosts, long msId) + throws DiscoveryException { + } + + @Override + public boolean matchHypervisor(String hypervisor) { + return hypervisor.equalsIgnoreCase(Hypervisor.HypervisorType.BareMetal.toString()); + } + + @Override + public HypervisorType getHypervisorType() { + return Hypervisor.HypervisorType.BareMetal; + } + + @Override + public HostVO createHostVOForConnectedAgent(HostVO host, StartupCommand[] cmd) { + // TODO Auto-generated method stub + return null; + } + + @Override + public HostVO createHostVOForDirectConnectAgent(HostVO host, StartupCommand[] startup, ServerResource resource, Map details, List hostTags) { + StartupCommand firstCmd = startup[0]; + if (!(firstCmd instanceof StartupRoutingCommand)) { + return null; + } + + StartupRoutingCommand ssCmd = ((StartupRoutingCommand)firstCmd); + if (ssCmd.getHypervisorType() != HypervisorType.BareMetal) { + return null; + } + + return _resourceMgr.fillRoutingHostVO(host, ssCmd, HypervisorType.BareMetal, details, hostTags); + } + + @Override + public DeleteHostAnswer deleteHost(HostVO host, boolean isForced, boolean isForceDeleteStorage) throws UnableDeleteHostException { + if (host.getType() != Host.Type.Routing || host.getHypervisorType() != HypervisorType.BareMetal) { + return null; + } + + List deadVms = _vmDao.listByLastHostId(host.getId()); + for (VMInstanceVO vm : deadVms) { + if (vm.getState() == State.Running || vm.getHostId() != null) { + throw new CloudRuntimeException("VM " + vm.getId() + "is still running on host " + host.getId()); + } + _vmDao.remove(vm.getId()); + } + + return new DeleteHostAnswer(true); + } + + @Override + protected HashMap buildConfigParams(HostVO host) { + HashMap params = super.buildConfigParams(host); + params.put("hostId", host.getId()); + params.put("ipaddress", host.getPrivateIpAddress()); + params.put("vmDao", _vmDao); + params.put("configDao", _configDao); + return params; + } + +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalGuru.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalGuru.java new file mode 100644 index 0000000000..540ab9a044 --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalGuru.java @@ -0,0 +1,92 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +// Apache License, Version 2.0 (the "License"); you may not use this +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// Automatically generated by addcopyright.py at 04/03/2012 +package com.cloud.baremetal.manager; + +import java.util.HashMap; +import java.util.Map; + +import javax.inject.Inject; + +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.hypervisor.HypervisorGuru; +import com.cloud.hypervisor.HypervisorGuruBase; +import com.cloud.storage.GuestOSVO; +import com.cloud.storage.dao.GuestOSDao; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.VMInstanceDao; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BareMetalGuru extends HypervisorGuruBase implements HypervisorGuru { + private static final Logger s_logger = LoggerFactory.getLogger(BareMetalGuru.class); + @Inject + GuestOSDao _guestOsDao; + @Inject + HostDao _hostDao; + @Inject + VMInstanceDao _vmDao; + + protected BareMetalGuru() { + super(); + } + + @Override + public HypervisorType getHypervisorType() { + return HypervisorType.BareMetal; + } + + @Override + public VirtualMachineTO implement(VirtualMachineProfile vm) { + VirtualMachineTO to = toVirtualMachineTO(vm); + + VMInstanceVO vo = _vmDao.findById(vm.getId()); + if (vo.getLastHostId() == null) { + to.setBootArgs(BaremetalManager.DO_PXE); + } + + Map details = new HashMap(); + details.put("template", vm.getTemplate().getUrl()); + to.setDetails(details); + + // Determine the VM's OS description + GuestOSVO guestOS = _guestOsDao.findById(vm.getVirtualMachine().getGuestOSId()); + to.setOs(guestOS.getDisplayName()); + + return to; + } + + @Override + public boolean trackVmHostChange() { + return false; + } + + @Override + public Map getClusterSettings(long vmId) { + return null; + } + +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalPlanner.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalPlanner.java new file mode 100644 index 0000000000..6f3a50acbc --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalPlanner.java @@ -0,0 +1,173 @@ +// 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.baremetal.manager; + +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import com.cloud.capacity.CapacityManager; +import com.cloud.dc.ClusterDetailsDao; +import com.cloud.dc.ClusterDetailsVO; +import com.cloud.dc.ClusterVO; +import com.cloud.dc.DataCenter; +import com.cloud.dc.Pod; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.dc.dao.HostPodDao; +import com.cloud.deploy.DeployDestination; +import com.cloud.deploy.DeploymentPlan; +import com.cloud.deploy.DeploymentPlanner; +import com.cloud.exception.InsufficientServerCapacityException; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.offering.ServiceOffering; +import com.cloud.org.Cluster; +import com.cloud.resource.ResourceManager; +import com.cloud.utils.component.AdapterBase; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineProfile; + +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BareMetalPlanner extends AdapterBase implements DeploymentPlanner { + private static final Logger s_logger = LoggerFactory.getLogger(BareMetalPlanner.class); + @Inject + protected DataCenterDao _dcDao; + @Inject + protected HostPodDao _podDao; + @Inject + protected ClusterDao _clusterDao; + @Inject + protected HostDao _hostDao; + @Inject + protected ConfigurationDao _configDao; + @Inject + protected CapacityManager _capacityMgr; + @Inject + protected ResourceManager _resourceMgr; + @Inject + protected ClusterDetailsDao _clusterDetailsDao; + + @Override + public DeployDestination plan(VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid) throws InsufficientServerCapacityException { + VirtualMachine vm = vmProfile.getVirtualMachine(); + ServiceOffering offering = vmProfile.getServiceOffering(); + String hostTag = null; + + String haVmTag = (String)vmProfile.getParameter(VirtualMachineProfile.Param.HaTag); + + if (vm.getLastHostId() != null && haVmTag == null) { + HostVO h = _hostDao.findById(vm.getLastHostId()); + DataCenter dc = _dcDao.findById(h.getDataCenterId()); + Pod pod = _podDao.findById(h.getPodId()); + Cluster c = _clusterDao.findById(h.getClusterId()); + s_logger.debug("Start baremetal vm " + vm.getId() + " on last stayed host " + h.getId()); + return new DeployDestination(dc, pod, c, h); + } + + if (haVmTag != null) { + hostTag = haVmTag; + } else if (offering.getHostTag() != null) { + String[] tags = offering.getHostTag().split(","); + if (tags.length > 0) { + hostTag = tags[0]; + } + } + + List clusters = _clusterDao.listByDcHyType(vm.getDataCenterId(), HypervisorType.BareMetal.toString()); + int cpu_requested; + long ram_requested; + HostVO target = null; + List hosts; + for (ClusterVO cluster : clusters) { + hosts = _resourceMgr.listAllUpAndEnabledHosts(Host.Type.Routing, cluster.getId(), cluster.getPodId(), cluster.getDataCenterId()); + if (hostTag != null) { + for (HostVO h : hosts) { + _hostDao.loadDetails(h); + if (h.getDetail("hostTag") != null && h.getDetail("hostTag").equalsIgnoreCase(hostTag)) { + target = h; + break; + } + } + } + } + + if (target == null) { + s_logger.warn("Cannot find host with tag " + hostTag + " use capacity from service offering"); + cpu_requested = offering.getCpu() * offering.getSpeed(); + ram_requested = offering.getRamSize() * 1024L * 1024L; + } else { + cpu_requested = target.getCpus() * target.getSpeed().intValue(); + ram_requested = target.getTotalMemory(); + } + + for (ClusterVO cluster : clusters) { + if (haVmTag == null) { + hosts = _resourceMgr.listAllUpAndEnabledNonHAHosts(Host.Type.Routing, cluster.getId(), cluster.getPodId(), cluster.getDataCenterId()); + } else { + s_logger.warn("Cannot find HA host with tag " + haVmTag + " in cluster id=" + cluster.getId() + ", pod id=" + cluster.getPodId() + ", data center id=" + + cluster.getDataCenterId()); + return null; + } + for (HostVO h : hosts) { + long cluster_id = h.getClusterId(); + ClusterDetailsVO cluster_detail_cpu = _clusterDetailsDao.findDetail(cluster_id, "cpuOvercommitRatio"); + ClusterDetailsVO cluster_detail_ram = _clusterDetailsDao.findDetail(cluster_id, "memoryOvercommitRatio"); + Float cpuOvercommitRatio = Float.parseFloat(cluster_detail_cpu.getValue()); + Float memoryOvercommitRatio = Float.parseFloat(cluster_detail_ram.getValue()); + + if (_capacityMgr.checkIfHostHasCapacity(h.getId(), cpu_requested, ram_requested, false, cpuOvercommitRatio, memoryOvercommitRatio, true)) { + s_logger.debug("Find host " + h.getId() + " has enough capacity"); + DataCenter dc = _dcDao.findById(h.getDataCenterId()); + Pod pod = _podDao.findById(h.getPodId()); + return new DeployDestination(dc, pod, cluster, h); + } + } + } + + s_logger.warn(String.format("Cannot find enough capacity(requested cpu=%1$s memory=%2$s)", cpu_requested, ram_requested)); + return null; + } + + @Override + public boolean canHandle(VirtualMachineProfile vm, DeploymentPlan plan, ExcludeList avoid) { + return vm.getHypervisorType() == HypervisorType.BareMetal; + } + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + return true; + } + + @Override + public boolean start() { + return true; + } + + @Override + public boolean stop() { + return true; + } + +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalTemplateAdapter.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalTemplateAdapter.java new file mode 100644 index 0000000000..a8eb35e32a --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalTemplateAdapter.java @@ -0,0 +1,205 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +// Apache License, Version 2.0 (the "License"); you may not use this +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// Automatically generated by addcopyright.py at 04/03/2012 +package com.cloud.baremetal.manager; + +import java.util.Date; +import java.util.List; + +import javax.inject.Inject; + +import com.cloud.configuration.Resource.ResourceType; +import com.cloud.dc.DataCenterVO; +import com.cloud.event.EventTypes; +import com.cloud.event.UsageEventVO; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.host.dao.HostDao; +import com.cloud.resource.ResourceManager; +import com.cloud.storage.TemplateProfile; +import com.cloud.storage.VMTemplateStorageResourceAssoc.Status; +import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.VMTemplateZoneVO; +import com.cloud.template.TemplateAdapter; +import com.cloud.template.TemplateAdapterBase; +import com.cloud.template.VirtualMachineTemplate.State; +import com.cloud.user.Account; +import com.cloud.utils.db.DB; +import com.cloud.utils.exception.CloudRuntimeException; + +import org.apache.cloudstack.api.command.user.iso.DeleteIsoCmd; +import org.apache.cloudstack.api.command.user.iso.RegisterIsoCmd; +import org.apache.cloudstack.api.command.user.template.RegisterTemplateCmd; +import org.apache.cloudstack.storage.command.TemplateOrVolumePostUploadCommand; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BareMetalTemplateAdapter extends TemplateAdapterBase implements TemplateAdapter { + private final static Logger s_logger = LoggerFactory.getLogger(BareMetalTemplateAdapter.class); + @Inject + HostDao _hostDao; + @Inject + ResourceManager _resourceMgr; + + @Override + public String getName() { + return TemplateAdapterType.BareMetal.getName(); + } + + @Override + public TemplateProfile prepare(RegisterTemplateCmd cmd) throws ResourceAllocationException { + return super.prepare(cmd); + } + + @Override + public TemplateProfile prepare(RegisterIsoCmd cmd) throws ResourceAllocationException { + throw new CloudRuntimeException("Baremetal doesn't support ISO template"); + } + + private void templateCreateUsage(VMTemplateVO template, long dcId) { + if (template.getAccountId() != Account.ACCOUNT_ID_SYSTEM) { + UsageEventVO usageEvent = + new UsageEventVO(EventTypes.EVENT_TEMPLATE_CREATE, template.getAccountId(), dcId, template.getId(), template.getName(), null, + template.getSourceTemplateId(), 0L); + _usageEventDao.persist(usageEvent); + } + } + + @Override + public VMTemplateVO create(TemplateProfile profile) { + VMTemplateVO template = persistTemplate(profile, State.Active); + Long zoneId = profile.getZoneId(); + + // create an entry at template_store_ref with store_id = null to represent that this template is ready for use. + TemplateDataStoreVO vmTemplateHost = + new TemplateDataStoreVO(null, template.getId(), new Date(), 100, Status.DOWNLOADED, null, null, null, null, template.getUrl()); + this._tmpltStoreDao.persist(vmTemplateHost); + + if (zoneId == null || zoneId == -1) { + List dcs = _dcDao.listAllIncludingRemoved(); + if (dcs != null && dcs.size() > 0) { + templateCreateUsage(template, dcs.get(0).getId()); + } + } else { + templateCreateUsage(template, zoneId); + } + + _resourceLimitMgr.incrementResourceCount(profile.getAccountId(), ResourceType.template); + return template; + } + + @Override + public List createTemplateForPostUpload(TemplateProfile profile) { + // TODO: support baremetal for postupload + return null; + } + + @Override + public TemplateProfile prepareDelete(DeleteIsoCmd cmd) { + throw new CloudRuntimeException("Baremetal doesn't support ISO, how the delete get here???"); + } + + @Override + @DB + public boolean delete(TemplateProfile profile) { + VMTemplateVO template = profile.getTemplate(); + Long templateId = template.getId(); + boolean success = true; + String zoneName; + + if (!template.isCrossZones() && profile.getZoneId() != null) { + zoneName = profile.getZoneId().toString(); + } else { + zoneName = "all zones"; + } + + s_logger.debug("Attempting to mark template host refs for template: " + template.getName() + " as destroyed in zone: " + zoneName); + Account account = _accountDao.findByIdIncludingRemoved(template.getAccountId()); + String eventType = EventTypes.EVENT_TEMPLATE_DELETE; + List templateHostVOs = this._tmpltStoreDao.listByTemplate(templateId); + + for (TemplateDataStoreVO vo : templateHostVOs) { + TemplateDataStoreVO lock = null; + try { + lock = _tmpltStoreDao.acquireInLockTable(vo.getId()); + if (lock == null) { + s_logger.debug("Failed to acquire lock when deleting templateDataStoreVO with ID: " + vo.getId()); + success = false; + break; + } + + vo.setDestroyed(true); + _tmpltStoreDao.update(vo.getId(), vo); + + } finally { + if (lock != null) { + _tmpltStoreDao.releaseFromLockTable(lock.getId()); + } + } + } + + if (profile.getZoneId() != null) { + UsageEventVO usageEvent = new UsageEventVO(eventType, account.getId(), profile.getZoneId(), templateId, null); + _usageEventDao.persist(usageEvent); + } else { + List dcs = _dcDao.listAllIncludingRemoved(); + for (DataCenterVO dc : dcs) { + UsageEventVO usageEvent = new UsageEventVO(eventType, account.getId(), dc.getId(), templateId, null); + _usageEventDao.persist(usageEvent); + } + } + + VMTemplateZoneVO templateZone = _tmpltZoneDao.findByZoneTemplate(profile.getZoneId(), templateId); + + if (templateZone != null) { + _tmpltZoneDao.remove(templateZone.getId()); + } + + s_logger.debug("Successfully marked template host refs for template: " + template.getName() + " as destroyed in zone: " + zoneName); + + // If there are no more non-destroyed template host entries for this template, delete it + if (success && (_tmpltStoreDao.listByTemplate(templateId).size() == 0)) { + long accountId = template.getAccountId(); + + VMTemplateVO lock = _tmpltDao.acquireInLockTable(templateId); + + try { + if (lock == null) { + s_logger.debug("Failed to acquire lock when deleting template with ID: " + templateId); + success = false; + } else if (_tmpltDao.remove(templateId)) { + // Decrement the number of templates and total secondary storage space used by the account. + _resourceLimitMgr.decrementResourceCount(accountId, ResourceType.template); + _resourceLimitMgr.recalculateResourceCount(accountId, template.getDomainId(), ResourceType.secondary_storage.getOrdinal()); + } + + } finally { + if (lock != null) { + _tmpltDao.releaseFromLockTable(lock.getId()); + } + } + s_logger.debug("Removed template: " + template.getName() + " because all of its template host refs were marked as destroyed."); + } + + return success; + } +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BaremetalManager.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BaremetalManager.java new file mode 100644 index 0000000000..d607698a83 --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BaremetalManager.java @@ -0,0 +1,32 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +package com.cloud.baremetal.manager; + +import com.cloud.utils.component.Manager; +import com.cloud.utils.component.PluggableService; + +import org.apache.cloudstack.api.BaremetalProvisionDoneNotificationCmd; + +public interface BaremetalManager extends Manager, PluggableService { + public static final String EchoSecurityGroupAgent = "EchoSecurityGroupAgent"; + public static final String ExternalBaremetalSystemUrl = "ExternalBaremetalSystemUrl"; + public static final String DO_PXE = "doPxe"; + + void notifyProvisionDone(BaremetalProvisionDoneNotificationCmd cmd); +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BaremetalManagerImpl.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BaremetalManagerImpl.java new file mode 100644 index 0000000000..f53cce32b3 --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BaremetalManagerImpl.java @@ -0,0 +1,156 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +package com.cloud.baremetal.manager; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.db.QueryBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.fsm.StateListener; +import com.cloud.utils.fsm.StateMachine2; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachine.Event; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.dao.VMInstanceDao; + +import org.apache.cloudstack.api.AddBaremetalHostCmd; +import org.apache.cloudstack.api.BaremetalProvisionDoneNotificationCmd; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BaremetalManagerImpl extends ManagerBase implements BaremetalManager, StateListener { + private static final Logger s_logger = LoggerFactory.getLogger(BaremetalManagerImpl.class); + + @Inject + protected HostDao _hostDao; + @Inject + protected VMInstanceDao vmDao; + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + VirtualMachine.State.getStateMachine().registerListener(this); + return true; + } + + @Override + public boolean start() { + return true; + } + + @Override + public boolean stop() { + return true; + } + + @Override + public String getName() { + return "Baremetal Manager"; + } + + @Override + public boolean preStateTransitionEvent(State oldState, Event event, State newState, VirtualMachine vo, boolean status, Object opaque) { + return false; + } + + @Override + public boolean postStateTransitionEvent(StateMachine2.Transition transition, VirtualMachine vo, boolean status, Object opaque) { + State newState = transition.getToState(); + State oldState = transition.getCurrentState(); + if (newState != State.Starting && newState != State.Error && newState != State.Expunging) { + return true; + } + + if (vo.getHypervisorType() != HypervisorType.BareMetal) { + return true; + } + + HostVO host = _hostDao.findById(vo.getHostId()); + if (host == null) { + s_logger.debug("Skip oldState " + oldState + " to " + "newState " + newState + " transimtion"); + return true; + } + _hostDao.loadDetails(host); + + if (newState == State.Starting) { + host.setDetail("vmName", vo.getInstanceName()); + s_logger.debug("Add vmName " + host.getDetail("vmName") + " to host " + host.getId() + " details"); + } else { + if (host.getDetail("vmName") != null && host.getDetail("vmName").equalsIgnoreCase(vo.getInstanceName())) { + s_logger.debug("Remove vmName " + host.getDetail("vmName") + " from host " + host.getId() + " details"); + host.getDetails().remove("vmName"); + } + } + _hostDao.saveDetails(host); + + return true; + } + + @Override + public List> getCommands() { + List> cmds = new ArrayList>(); + cmds.add(AddBaremetalHostCmd.class); + cmds.add(BaremetalProvisionDoneNotificationCmd.class); + return cmds; + } + + @Override + public void notifyProvisionDone(BaremetalProvisionDoneNotificationCmd cmd) { + QueryBuilder hq = QueryBuilder.create(HostVO.class); + hq.and(hq.entity().getPrivateMacAddress(), SearchCriteria.Op.EQ, cmd.getMac()); + HostVO host = hq.find(); + if (host == null) { + throw new CloudRuntimeException(String.format("cannot find host[mac:%s]", cmd.getMac())); + } + + _hostDao.loadDetails(host); + String vmName = host.getDetail("vmName"); + if (vmName == null) { + throw new CloudRuntimeException(String.format("cannot find any baremetal instance running on host[mac:%s]", cmd.getMac())); + } + + QueryBuilder vmq = QueryBuilder.create(VMInstanceVO.class); + vmq.and(vmq.entity().getInstanceName(), SearchCriteria.Op.EQ, vmName); + VMInstanceVO vm = vmq.find(); + + if (vm == null) { + throw new CloudRuntimeException(String.format("cannot find baremetal instance[name:%s]", vmName)); + } + + if (State.Starting != vm.getState()) { + throw new CloudRuntimeException(String.format("baremetal instance[name:%s, state:%s] is not in state of Starting", vmName, vm.getState())); + } + + vm.setState(State.Running); + vm.setLastHostId(vm.getHostId()); + vmDao.update(vm.getId(), vm); + s_logger.debug(String.format("received baremetal provision done notification for vm[id:%s name:%s] running on host[mac:%s, ip:%s]", + vm.getId(), vm.getInstanceName(), host.getPrivateMacAddress(), host.getPrivateIpAddress())); + } +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BaremetalVlanManager.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BaremetalVlanManager.java new file mode 100644 index 0000000000..d6c8cc8d13 --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BaremetalVlanManager.java @@ -0,0 +1,44 @@ +// 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.baremetal.manager; + +import com.cloud.baremetal.networkservice.BaremetalRctResponse; +import com.cloud.baremetal.networkservice.BaremetalSwitchBackend; +import com.cloud.deploy.DeployDestination; +import com.cloud.network.Network; +import com.cloud.utils.component.Manager; +import com.cloud.utils.component.PluggableService; +import com.cloud.vm.VirtualMachineProfile; + +import org.apache.cloudstack.api.AddBaremetalRctCmd; +import org.apache.cloudstack.api.DeleteBaremetalRctCmd; + +public interface BaremetalVlanManager extends Manager, PluggableService { + + BaremetalRctResponse addRct(AddBaremetalRctCmd cmd); + + void prepareVlan(Network nw, DeployDestination destHost); + + void releaseVlan(Network nw, VirtualMachineProfile vm); + + void registerSwitchBackend(BaremetalSwitchBackend backend); + + void deleteRct(DeleteBaremetalRctCmd cmd); + + BaremetalRctResponse listRct(); +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BaremetalVlanManagerImpl.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BaremetalVlanManagerImpl.java new file mode 100644 index 0000000000..fcb4099244 --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BaremetalVlanManagerImpl.java @@ -0,0 +1,271 @@ +// 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.baremetal.manager; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import javax.inject.Inject; + +import com.cloud.baremetal.database.BaremetalRctDao; +import com.cloud.baremetal.database.BaremetalRctVO; +import com.cloud.baremetal.networkservice.BaremetalRctResponse; +import com.cloud.baremetal.networkservice.BaremetalSwitchBackend; +import com.cloud.baremetal.networkservice.BaremetalVlanStruct; +import com.cloud.deploy.DeployDestination; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.network.Network; +import com.cloud.network.Networks; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.AccountVO; +import com.cloud.user.User; +import com.cloud.user.UserVO; +import com.cloud.user.dao.AccountDao; +import com.cloud.user.dao.UserDao; +import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.db.QueryBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VirtualMachineProfile; +import com.google.gson.Gson; + +import org.apache.cloudstack.api.AddBaremetalRctCmd; +import org.apache.cloudstack.api.DeleteBaremetalRctCmd; +import org.apache.cloudstack.api.ListBaremetalRctCmd; +import org.apache.cloudstack.utils.baremetal.BaremetalUtils; +import org.springframework.web.client.RestTemplate; + +/** + * Created by frank on 5/8/14. + */ +public class BaremetalVlanManagerImpl extends ManagerBase implements BaremetalVlanManager { + private Gson gson = new Gson(); + + @Inject + private BaremetalRctDao rctDao; + @Inject + private HostDao hostDao; + @Inject + private AccountDao acntDao; + @Inject + private UserDao userDao; + @Inject + private AccountManager acntMgr; + + private Map backends; + + private class RackPair { + BaremetalRct.Rack rack; + BaremetalRct.HostEntry host; + } + + public void setBackends(Map backends) { + this.backends = backends; + } + + @Override + public BaremetalRctResponse addRct(AddBaremetalRctCmd cmd) { + try { + List existings = rctDao.listAll(); + if (!existings.isEmpty()) { + throw new CloudRuntimeException(String.format("there is some RCT existing. A CloudStack deployment accepts only one RCT")); + } + URL url = new URL(cmd.getRctUrl()); + RestTemplate rest = new RestTemplate(); + String rctStr = rest.getForObject(url.toString(), String.class); + + // validate it's right format + BaremetalRct rct = gson.fromJson(rctStr, BaremetalRct.class); + QueryBuilder sc = QueryBuilder.create(BaremetalRctVO.class); + sc.and(sc.entity().getUrl(), SearchCriteria.Op.EQ, cmd.getRctUrl()); + BaremetalRctVO vo = sc.find(); + if (vo == null) { + vo = new BaremetalRctVO(); + vo.setRct(gson.toJson(rct)); + vo.setUrl(cmd.getRctUrl()); + vo = rctDao.persist(vo); + } else { + vo.setRct(gson.toJson(rct)); + rctDao.update(vo.getId(), vo); + } + + BaremetalRctResponse rsp = new BaremetalRctResponse(); + rsp.setUrl(vo.getUrl()); + rsp.setId(vo.getUuid()); + rsp.setObjectName("baremetalrct"); + return rsp; + } catch (MalformedURLException e) { + throw new IllegalArgumentException(String.format("%s is not a legal http url", cmd.getRctUrl())); + } + } + + @Override + public void prepareVlan(Network nw, DeployDestination destHost) { + List vos = rctDao.listAll(); + if (vos.isEmpty()) { + throw new CloudRuntimeException("no rack configuration found, please call addBaremetalRct to add one"); + } + + BaremetalRctVO vo = vos.get(0); + BaremetalRct rct = gson.fromJson(vo.getRct(), BaremetalRct.class); + + RackPair rp = findRack(rct, destHost.getHost().getPrivateMacAddress()); + if (rp == null) { + throw new CloudRuntimeException(String.format("cannot find any rack contains host[mac:%s], please double check your rack configuration file, update it and call addBaremetalRct again", destHost.getHost().getPrivateMacAddress())); + } + + int vlan = Integer.parseInt(Networks.BroadcastDomainType.getValue(nw.getBroadcastUri())); + BaremetalSwitchBackend backend = getSwitchBackend(rp.rack.getL2Switch().getType()); + BaremetalVlanStruct struct = new BaremetalVlanStruct(); + struct.setHostMac(rp.host.getMac()); + struct.setPort(rp.host.getPort()); + struct.setSwitchIp(rp.rack.getL2Switch().getIp()); + struct.setSwitchPassword(rp.rack.getL2Switch().getPassword()); + struct.setSwitchType(rp.rack.getL2Switch().getType()); + struct.setSwitchUsername(rp.rack.getL2Switch().getUsername()); + struct.setVlan(vlan); + backend.prepareVlan(struct); + } + + @Override + public void releaseVlan(Network nw, VirtualMachineProfile vm) { + List vos = rctDao.listAll(); + if (vos.isEmpty()) { + throw new CloudRuntimeException("no rack configuration found, please call addBaremetalRct to add one"); + } + + BaremetalRctVO vo = vos.get(0); + BaremetalRct rct = gson.fromJson(vo.getRct(), BaremetalRct.class); + HostVO host = hostDao.findById(vm.getVirtualMachine().getHostId()); + RackPair rp = findRack(rct, host.getPrivateMacAddress()); + assert rp != null : String.format("where is my rack???"); + + int vlan = Integer.parseInt(Networks.BroadcastDomainType.getValue(nw.getBroadcastUri())); + BaremetalVlanStruct struct = new BaremetalVlanStruct(); + struct.setHostMac(rp.host.getMac()); + struct.setPort(rp.host.getPort()); + struct.setSwitchIp(rp.rack.getL2Switch().getIp()); + struct.setSwitchPassword(rp.rack.getL2Switch().getPassword()); + struct.setSwitchType(rp.rack.getL2Switch().getType()); + struct.setSwitchUsername(rp.rack.getL2Switch().getUsername()); + struct.setVlan(vlan); + BaremetalSwitchBackend backend = getSwitchBackend(rp.rack.getL2Switch().getType()); + backend.removePortFromVlan(struct); + } + + @Override + public void registerSwitchBackend(BaremetalSwitchBackend backend) { + backends.put(backend.getSwitchBackendType(), backend); + } + + @Override + public void deleteRct(DeleteBaremetalRctCmd cmd) { + rctDao.remove(cmd.getId()); + } + + @Override + public BaremetalRctResponse listRct() { + List vos = rctDao.listAll(); + if (!vos.isEmpty()) { + BaremetalRctVO vo = vos.get(0); + BaremetalRctResponse rsp = new BaremetalRctResponse(); + rsp.setId(vo.getUuid()); + rsp.setUrl(vo.getUrl()); + rsp.setObjectName("baremetalrct"); + return rsp; + } + return null; + } + + private BaremetalSwitchBackend getSwitchBackend(String type) { + BaremetalSwitchBackend backend = backends.get(type); + if (backend == null) { + throw new CloudRuntimeException(String.format("cannot find switch backend[type:%s]", type)); + } + return backend; + } + + private RackPair findRack(BaremetalRct rct, String mac) { + for (BaremetalRct.Rack rack : rct.getRacks()) { + for (BaremetalRct.HostEntry host : rack.getHosts()) { + if (mac.equals(host.getMac())) { + RackPair p = new RackPair(); + p.host = host; + p.rack = rack; + return p; + } + } + } + return null; + } + + @Override + public String getName() { + return "Baremetal Vlan Manager"; + } + + + @Override + public List> getCommands() { + List> cmds = new ArrayList>(); + cmds.add(AddBaremetalRctCmd.class); + cmds.add(ListBaremetalRctCmd.class); + cmds.add(DeleteBaremetalRctCmd.class); + return cmds; + } + + @Override + public boolean start() { + QueryBuilder acntq = QueryBuilder.create(AccountVO.class); + acntq.and(acntq.entity().getAccountName(), SearchCriteria.Op.EQ, BaremetalUtils.BAREMETAL_SYSTEM_ACCOUNT_NAME); + AccountVO acnt = acntq.find(); + if (acnt != null) { + return true; + } + + acnt = new AccountVO(); + acnt.setAccountName(BaremetalUtils.BAREMETAL_SYSTEM_ACCOUNT_NAME); + acnt.setUuid(UUID.randomUUID().toString()); + acnt.setState(Account.State.enabled); + acnt.setDomainId(1); + acnt = acntDao.persist(acnt); + + UserVO user = new UserVO(); + user.setState(Account.State.enabled); + user.setUuid(UUID.randomUUID().toString()); + user.setAccountId(acnt.getAccountId()); + user.setUsername(BaremetalUtils.BAREMETAL_SYSTEM_ACCOUNT_NAME); + user.setFirstname(BaremetalUtils.BAREMETAL_SYSTEM_ACCOUNT_NAME); + user.setLastname(BaremetalUtils.BAREMETAL_SYSTEM_ACCOUNT_NAME); + user.setPassword(UUID.randomUUID().toString()); + user.setSource(User.Source.UNKNOWN); + user = userDao.persist(user); + + String[] keys = acntMgr.createApiKeyAndSecretKey(user.getId()); + user.setApiKey(keys[0]); + user.setSecretKey(keys[1]); + userDao.update(user.getId(), user); + return true; + } +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BareMetalPingServiceImpl.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BareMetalPingServiceImpl.java new file mode 100644 index 0000000000..b29257095e --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BareMetalPingServiceImpl.java @@ -0,0 +1,281 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +// Apache License, Version 2.0 (the "License"); you may not use this +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// Automatically generated by addcopyright.py at 04/03/2012 +package com.cloud.baremetal.networkservice; + +import java.net.URI; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.baremetal.IpmISetBootDevCommand; +import com.cloud.agent.api.baremetal.IpmISetBootDevCommand.BootDev; +import com.cloud.agent.api.baremetal.PrepareCreateTemplateCommand; +import com.cloud.agent.api.baremetal.PreparePxeServerAnswer; +import com.cloud.agent.api.baremetal.PreparePxeServerCommand; +import com.cloud.baremetal.database.BaremetalPxeDao; +import com.cloud.baremetal.database.BaremetalPxeVO; +import com.cloud.baremetal.networkservice.BaremetalPxeManager.BaremetalPxeType; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.HostPodVO; +import com.cloud.deploy.DeployDestination; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDetailsDao; +import com.cloud.network.Network; +import com.cloud.network.PhysicalNetworkServiceProvider; +import com.cloud.network.dao.PhysicalNetworkDao; +import com.cloud.network.dao.PhysicalNetworkServiceProviderDao; +import com.cloud.network.dao.PhysicalNetworkServiceProviderVO; +import com.cloud.network.dao.PhysicalNetworkVO; +import com.cloud.resource.ResourceManager; +import com.cloud.resource.ServerResource; +import com.cloud.uservm.UserVm; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.QueryBuilder; +import com.cloud.utils.db.SearchCriteria.Op; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; +import com.cloud.vm.ReservationContext; +import com.cloud.vm.VirtualMachineProfile; + +import org.apache.cloudstack.api.AddBaremetalPxeCmd; +import org.apache.cloudstack.api.AddBaremetalPxePingServerCmd; +import org.apache.cloudstack.api.ListBaremetalPxeServersCmd; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BareMetalPingServiceImpl extends BareMetalPxeServiceBase implements BaremetalPxeService { + private static final Logger s_logger = LoggerFactory.getLogger(BareMetalPingServiceImpl.class); + @Inject + ResourceManager _resourceMgr; + @Inject + PhysicalNetworkDao _physicalNetworkDao; + @Inject + PhysicalNetworkServiceProviderDao _physicalNetworkServiceProviderDao; + @Inject + HostDetailsDao _hostDetailsDao; + @Inject + BaremetalPxeDao _pxeDao; + + @Override + public boolean prepare(VirtualMachineProfile profile, NicProfile pxeNic, Network network, DeployDestination dest, ReservationContext context) { + QueryBuilder sc = QueryBuilder.create(BaremetalPxeVO.class); + sc.and(sc.entity().getDeviceType(), Op.EQ, BaremetalPxeType.PING.toString()); + sc.and(sc.entity().getPodId(), Op.EQ, dest.getPod().getId()); + BaremetalPxeVO pxeVo = sc.find(); + if (pxeVo == null) { + throw new CloudRuntimeException("No PING PXE server found in pod: " + dest.getPod().getId() + ", you need to add it before starting VM"); + } + long pxeServerId = pxeVo.getHostId(); + + String mac = pxeNic.getMacAddress(); + String ip = pxeNic.getIPv4Address(); + String gateway = pxeNic.getIPv4Gateway(); + String mask = pxeNic.getIPv4Netmask(); + String dns = pxeNic.getIPv4Dns1(); + if (dns == null) { + dns = pxeNic.getIPv4Dns2(); + } + + try { + String tpl = profile.getTemplate().getUrl(); + assert tpl != null : "How can a null template get here!!!"; + PreparePxeServerCommand cmd = + new PreparePxeServerCommand(ip, mac, mask, gateway, dns, tpl, profile.getVirtualMachine().getInstanceName(), dest.getHost().getName()); + PreparePxeServerAnswer ans = (PreparePxeServerAnswer)_agentMgr.send(pxeServerId, cmd); + if (!ans.getResult()) { + s_logger.warn("Unable tot program PXE server: " + pxeVo.getId() + " because " + ans.getDetails()); + return false; + } + + IpmISetBootDevCommand bootCmd = new IpmISetBootDevCommand(BootDev.pxe); + Answer anw = _agentMgr.send(dest.getHost().getId(), bootCmd); + if (!anw.getResult()) { + s_logger.warn("Unable to set host: " + dest.getHost().getId() + " to PXE boot because " + anw.getDetails()); + } + + return anw.getResult(); + } catch (Exception e) { + s_logger.warn("Cannot prepare PXE server", e); + return false; + } + } + + @Override + public boolean prepareCreateTemplate(Long pxeServerId, UserVm vm, String templateUrl) { + List nics = _nicDao.listByVmId(vm.getId()); + if (nics.size() != 1) { + throw new CloudRuntimeException("Wrong nic number " + nics.size() + " of vm " + vm.getId()); + } + + /* use last host id when VM stopped */ + Long hostId = (vm.getHostId() == null ? vm.getLastHostId() : vm.getHostId()); + HostVO host = _hostDao.findById(hostId); + DataCenterVO dc = _dcDao.findById(host.getDataCenterId()); + NicVO nic = nics.get(0); + String mask = nic.getIPv4Netmask(); + String mac = nic.getMacAddress(); + String ip = nic.getIPv4Address(); + String gateway = nic.getIPv4Gateway(); + String dns = dc.getDns1(); + if (dns == null) { + dns = dc.getDns2(); + } + + try { + PrepareCreateTemplateCommand cmd = new PrepareCreateTemplateCommand(ip, mac, mask, gateway, dns, templateUrl); + Answer ans = _agentMgr.send(pxeServerId, cmd); + return ans.getResult(); + } catch (Exception e) { + s_logger.debug("Prepare for creating baremetal template failed", e); + return false; + } + } + + @Override + @DB + public BaremetalPxeVO addPxeServer(AddBaremetalPxeCmd cmd) { + AddBaremetalPxePingServerCmd pcmd = (AddBaremetalPxePingServerCmd)cmd; + + PhysicalNetworkVO pNetwork = null; + long zoneId; + + if (cmd.getPhysicalNetworkId() == null || cmd.getUrl() == null || cmd.getUsername() == null || cmd.getPassword() == null) { + throw new IllegalArgumentException("At least one of the required parameters(physical network id, url, username, password) is null"); + } + + pNetwork = _physicalNetworkDao.findById(cmd.getPhysicalNetworkId()); + if (pNetwork == null) { + throw new IllegalArgumentException("Could not find phyical network with ID: " + cmd.getPhysicalNetworkId()); + } + zoneId = pNetwork.getDataCenterId(); + + PhysicalNetworkServiceProviderVO ntwkSvcProvider = + _physicalNetworkServiceProviderDao.findByServiceProvider(pNetwork.getId(), BaremetalPxeManager.BAREMETAL_PXE_SERVICE_PROVIDER.getName()); + if (ntwkSvcProvider == null) { + throw new CloudRuntimeException("Network Service Provider: " + BaremetalPxeManager.BAREMETAL_PXE_SERVICE_PROVIDER.getName() + + " is not enabled in the physical network: " + cmd.getPhysicalNetworkId() + "to add this device"); + } else if (ntwkSvcProvider.getState() == PhysicalNetworkServiceProvider.State.Shutdown) { + throw new CloudRuntimeException("Network Service Provider: " + ntwkSvcProvider.getProviderName() + " is in shutdown state in the physical network: " + + cmd.getPhysicalNetworkId() + "to add this device"); + } + + HostPodVO pod = _podDao.findById(cmd.getPodId()); + if (pod == null) { + throw new IllegalArgumentException("Could not find pod with ID: " + cmd.getPodId()); + } + + List pxes = _resourceMgr.listAllUpAndEnabledHosts(Host.Type.BaremetalPxe, null, cmd.getPodId(), zoneId); + if (pxes.size() != 0) { + throw new IllegalArgumentException("Already had a PXE server in Pod: " + cmd.getPodId() + " zone: " + zoneId); + } + + String storageServerIp = pcmd.getPingStorageServerIp(); + if (storageServerIp == null) { + throw new IllegalArgumentException("No IP for storage server specified"); + } + String pingDir = pcmd.getPingDir(); + if (pingDir == null) { + throw new IllegalArgumentException("No direcotry for storage server specified"); + } + String tftpDir = pcmd.getTftpDir(); + if (tftpDir == null) { + throw new IllegalArgumentException("No TFTP directory specified"); + } + + String cifsUsername = pcmd.getPingStorageServerUserName(); + if (cifsUsername == null || cifsUsername.equalsIgnoreCase("")) { + cifsUsername = "xxx"; + } + String cifsPassword = pcmd.getPingStorageServerPassword(); + if (cifsPassword == null || cifsPassword.equalsIgnoreCase("")) { + cifsPassword = "xxx"; + } + + URI uri; + try { + uri = new URI(cmd.getUrl()); + } catch (Exception e) { + s_logger.debug(e.toString()); + throw new IllegalArgumentException(e.getMessage()); + } + String ipAddress = uri.getHost(); + + String guid = getPxeServerGuid(Long.toString(zoneId) + "-" + pod.getId(), BaremetalPxeType.PING.toString(), ipAddress); + + ServerResource resource = null; + Map params = new HashMap(); + params.put(BaremetalPxeService.PXE_PARAM_ZONE, Long.toString(zoneId)); + params.put(BaremetalPxeService.PXE_PARAM_POD, String.valueOf(pod.getId())); + params.put(BaremetalPxeService.PXE_PARAM_IP, ipAddress); + params.put(BaremetalPxeService.PXE_PARAM_USERNAME, cmd.getUsername()); + params.put(BaremetalPxeService.PXE_PARAM_PASSWORD, cmd.getPassword()); + params.put(BaremetalPxeService.PXE_PARAM_PING_STORAGE_SERVER_IP, storageServerIp); + params.put(BaremetalPxeService.PXE_PARAM_PING_ROOT_DIR, pingDir); + params.put(BaremetalPxeService.PXE_PARAM_TFTP_DIR, tftpDir); + params.put(BaremetalPxeService.PXE_PARAM_PING_STORAGE_SERVER_USERNAME, cifsUsername); + params.put(BaremetalPxeService.PXE_PARAM_PING_STORAGE_SERVER_PASSWORD, cifsPassword); + params.put(BaremetalPxeService.PXE_PARAM_GUID, guid); + + resource = new BaremetalPingPxeResource(); + try { + resource.configure("PING PXE resource", params); + } catch (Exception e) { + s_logger.debug(e.toString()); + throw new CloudRuntimeException(e.getMessage()); + } + + Host pxeServer = _resourceMgr.addHost(zoneId, resource, Host.Type.BaremetalPxe, params); + if (pxeServer == null) { + throw new CloudRuntimeException("Cannot add PXE server as a host"); + } + + BaremetalPxeVO vo = new BaremetalPxeVO(); + vo.setHostId(pxeServer.getId()); + vo.setNetworkServiceProviderId(ntwkSvcProvider.getId()); + vo.setPodId(pod.getId()); + vo.setPhysicalNetworkId(pcmd.getPhysicalNetworkId()); + vo.setDeviceType(BaremetalPxeType.PING.toString()); + _pxeDao.persist(vo); + return vo; + } + + @Override + public BaremetalPxeResponse getApiResponse(BaremetalPxeVO vo) { + return null; + } + + @Override + public List listPxeServers(ListBaremetalPxeServersCmd cmd) { + return null; + } + + @Override + public String getPxeServiceType() { + return BaremetalPxeManager.BaremetalPxeType.PING.toString(); + } +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BareMetalResourceBase.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BareMetalResourceBase.java new file mode 100644 index 0000000000..cb5815389f --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BareMetalResourceBase.java @@ -0,0 +1,651 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +// Apache License, Version 2.0 (the "License"); you may not use this +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// Automatically generated by addcopyright.py at 04/03/2012 +package com.cloud.baremetal.networkservice; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import javax.naming.ConfigurationException; + +import com.cloud.agent.IAgentControl; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.CheckNetworkAnswer; +import com.cloud.agent.api.CheckNetworkCommand; +import com.cloud.agent.api.CheckVirtualMachineAnswer; +import com.cloud.agent.api.CheckVirtualMachineCommand; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.HostVmStateReportEntry; +import com.cloud.agent.api.MaintainAnswer; +import com.cloud.agent.api.MaintainCommand; +import com.cloud.agent.api.MigrateAnswer; +import com.cloud.agent.api.MigrateCommand; +import com.cloud.agent.api.PingCommand; +import com.cloud.agent.api.PingRoutingCommand; +import com.cloud.agent.api.PrepareForMigrationAnswer; +import com.cloud.agent.api.PrepareForMigrationCommand; +import com.cloud.agent.api.ReadyAnswer; +import com.cloud.agent.api.ReadyCommand; +import com.cloud.agent.api.RebootAnswer; +import com.cloud.agent.api.RebootCommand; +import com.cloud.agent.api.SecurityGroupRulesCmd; +import com.cloud.agent.api.StartAnswer; +import com.cloud.agent.api.StartCommand; +import com.cloud.agent.api.StartupCommand; +import com.cloud.agent.api.StartupRoutingCommand; +import com.cloud.agent.api.StopAnswer; +import com.cloud.agent.api.StopCommand; +import com.cloud.agent.api.baremetal.IpmISetBootDevCommand; +import com.cloud.agent.api.baremetal.IpmISetBootDevCommand.BootDev; +import com.cloud.agent.api.baremetal.IpmiBootorResetCommand; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.baremetal.manager.BaremetalManager; +import com.cloud.configuration.Config; +import com.cloud.host.Host.Type; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.resource.ServerResource; +import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.db.QueryBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.script.OutputInterpreter; +import com.cloud.utils.script.Script; +import com.cloud.utils.script.Script2; +import com.cloud.utils.script.Script2.ParamType; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachine.PowerState; +import com.cloud.vm.dao.VMInstanceDao; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BareMetalResourceBase extends ManagerBase implements ServerResource { + private static final Logger s_logger = LoggerFactory.getLogger(BareMetalResourceBase.class); + protected String _uuid; + protected String _zone; + protected String _pod; + protected Long hostId; + protected String _cluster; + protected long _memCapacity; + protected long _cpuCapacity; + protected long _cpuNum; + protected String _mac; + protected String _username; + protected String _password; + protected String _ip; + protected boolean _isEchoScAgent; + protected IAgentControl _agentControl; + protected Script2 _pingCommand; + protected Script2 _setPxeBootCommand; + protected Script2 _setDiskBootCommand; + protected Script2 _rebootCommand; + protected Script2 _getStatusCommand; + protected Script2 _powerOnCommand; + protected Script2 _powerOffCommand; + protected Script2 _forcePowerOffCommand; + protected Script2 _bootOrRebootCommand; + protected String _vmName; + protected int ipmiRetryTimes = 5; + protected boolean provisionDoneNotificationOn = false; + protected int isProvisionDoneNotificationTimeout = 1800; + + protected ConfigurationDao configDao; + protected VMInstanceDao vmDao; + + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + setName(name); + _uuid = (String) params.get("guid"); + try { + _memCapacity = Long.parseLong((String) params.get(ApiConstants.MEMORY)) * 1024L * 1024L; + _cpuCapacity = Long.parseLong((String) params.get(ApiConstants.CPU_SPEED)); + _cpuNum = Long.parseLong((String) params.get(ApiConstants.CPU_NUMBER)); + } catch (NumberFormatException e) { + throw new ConfigurationException(String.format("Unable to parse number of CPU or memory capacity " + + "or cpu capacity(cpu number = %1$s memCapacity=%2$s, cpuCapacity=%3$s", params.get(ApiConstants.CPU_NUMBER), + params.get(ApiConstants.MEMORY), params.get(ApiConstants.CPU_SPEED))); + } + + _zone = (String) params.get("zone"); + _pod = (String) params.get("pod"); + _cluster = (String) params.get("cluster"); + hostId = (Long) params.get("hostId"); + _ip = (String) params.get(ApiConstants.PRIVATE_IP); + _mac = (String) params.get(ApiConstants.HOST_MAC); + _username = (String) params.get(ApiConstants.USERNAME); + _password = (String) params.get(ApiConstants.PASSWORD); + _vmName = (String) params.get("vmName"); + String echoScAgent = (String) params.get(BaremetalManager.EchoSecurityGroupAgent); + vmDao = (VMInstanceDao) params.get("vmDao"); + configDao = (ConfigurationDao) params.get("configDao"); + + if (_pod == null) { + throw new ConfigurationException("Unable to get the pod"); + } + + if (_cluster == null) { + throw new ConfigurationException("Unable to get the pod"); + } + + if (_ip == null) { + throw new ConfigurationException("Unable to get the host address"); + } + + if (_mac.equalsIgnoreCase("unknown")) { + throw new ConfigurationException("Unable to get the host mac address"); + } + + if (_mac.split(":").length != 6) { + throw new ConfigurationException("Wrong MAC format(" + _mac + + "). It must be in format of for example 00:11:ba:33:aa:dd which is not case sensitive"); + } + + if (_uuid == null) { + throw new ConfigurationException("Unable to get the uuid"); + } + + if (echoScAgent != null) { + _isEchoScAgent = Boolean.valueOf(echoScAgent); + } + + String ipmiIface = "default"; + try { + ipmiIface = configDao.getValue(Config.BaremetalIpmiLanInterface.key()); + } catch (Exception e) { + s_logger.debug(e.getMessage(), e); + } + + try { + ipmiRetryTimes = Integer.parseInt(configDao.getValue(Config.BaremetalIpmiRetryTimes.key())); + } catch (Exception e) { + s_logger.debug(e.getMessage(), e); + } + + try { + provisionDoneNotificationOn = Boolean.valueOf(configDao.getValue(Config.BaremetalProvisionDoneNotificationEnabled.key())); + isProvisionDoneNotificationTimeout = Integer.parseInt(configDao.getValue(Config.BaremetalProvisionDoneNotificationTimeout.key())); + } catch (Exception e) { + s_logger.debug(e.getMessage(), e); + } + + String injectScript = "scripts/util/ipmi.py"; + String scriptPath = Script.findScript("", injectScript); + if (scriptPath == null) { + throw new ConfigurationException("Cannot find ping script " + scriptPath); + } + String pythonPath = "/usr/bin/python"; + _pingCommand = new Script2(pythonPath, s_logger); + _pingCommand.add(scriptPath); + _pingCommand.add("ping"); + _pingCommand.add("interface=" + ipmiIface); + _pingCommand.add("hostname=" + _ip); + _pingCommand.add("usrname=" + _username); + _pingCommand.add("password=" + _password, ParamType.PASSWORD); + + _setPxeBootCommand = new Script2(pythonPath, s_logger); + _setPxeBootCommand.add(scriptPath); + _setPxeBootCommand.add("boot_dev"); + _setPxeBootCommand.add("interface=" + ipmiIface); + _setPxeBootCommand.add("hostname=" + _ip); + _setPxeBootCommand.add("usrname=" + _username); + _setPxeBootCommand.add("password=" + _password, ParamType.PASSWORD); + _setPxeBootCommand.add("dev=pxe"); + + _setDiskBootCommand = new Script2(pythonPath, s_logger); + _setDiskBootCommand.add(scriptPath); + _setDiskBootCommand.add("boot_dev"); + _setDiskBootCommand.add("interface=" + ipmiIface); + _setDiskBootCommand.add("hostname=" + _ip); + _setDiskBootCommand.add("usrname=" + _username); + _setDiskBootCommand.add("password=" + _password, ParamType.PASSWORD); + _setDiskBootCommand.add("dev=disk"); + + _rebootCommand = new Script2(pythonPath, s_logger); + _rebootCommand.add(scriptPath); + _rebootCommand.add("reboot"); + _rebootCommand.add("interface=" + ipmiIface); + _rebootCommand.add("hostname=" + _ip); + _rebootCommand.add("usrname=" + _username); + _rebootCommand.add("password=" + _password, ParamType.PASSWORD); + + _getStatusCommand = new Script2(pythonPath, s_logger); + _getStatusCommand.add(scriptPath); + _getStatusCommand.add("ping"); + _getStatusCommand.add("interface=" + ipmiIface); + _getStatusCommand.add("hostname=" + _ip); + _getStatusCommand.add("usrname=" + _username); + _getStatusCommand.add("password=" + _password, ParamType.PASSWORD); + + _powerOnCommand = new Script2(pythonPath, s_logger); + _powerOnCommand.add(scriptPath); + _powerOnCommand.add("power"); + _powerOnCommand.add("interface=" + ipmiIface); + _powerOnCommand.add("hostname=" + _ip); + _powerOnCommand.add("usrname=" + _username); + _powerOnCommand.add("password=" + _password, ParamType.PASSWORD); + _powerOnCommand.add("action=on"); + + _powerOffCommand = new Script2(pythonPath, s_logger); + _powerOffCommand.add(scriptPath); + _powerOffCommand.add("power"); + _powerOffCommand.add("interface=" + ipmiIface); + _powerOffCommand.add("hostname=" + _ip); + _powerOffCommand.add("usrname=" + _username); + _powerOffCommand.add("password=" + _password, ParamType.PASSWORD); + _powerOffCommand.add("action=soft"); + + _forcePowerOffCommand = new Script2(pythonPath, s_logger); + _forcePowerOffCommand.add(scriptPath); + _forcePowerOffCommand.add("power"); + _forcePowerOffCommand.add("interface=" + ipmiIface); + _forcePowerOffCommand.add("hostname=" + _ip); + _forcePowerOffCommand.add("usrname=" + _username); + _forcePowerOffCommand.add("password=" + _password, ParamType.PASSWORD); + _forcePowerOffCommand.add("action=off"); + + _bootOrRebootCommand = new Script2(pythonPath, s_logger); + _bootOrRebootCommand.add(scriptPath); + _bootOrRebootCommand.add("boot_or_reboot"); + _bootOrRebootCommand.add("interface=" + ipmiIface); + _bootOrRebootCommand.add("hostname=" + _ip); + _bootOrRebootCommand.add("usrname=" + _username); + _bootOrRebootCommand.add("password=" + _password, ParamType.PASSWORD); + + return true; + } + + protected boolean doScript(Script cmd) { + return doScript(cmd, null); + } + + protected boolean doScript(Script cmd, int retry) { + return doScript(cmd, null, retry); + } + + protected boolean doScript(Script cmd, OutputInterpreter interpreter) { + return doScript(cmd, interpreter, ipmiRetryTimes); + } + + protected boolean doScript(Script cmd, OutputInterpreter interpreter, int retry) { + String res = null; + while (retry-- > 0) { + if (interpreter == null) { + res = cmd.execute(); + } else { + res = cmd.execute(interpreter); + } + if (res != null && res.startsWith("Error: Unable to establish LAN")) { + s_logger.warn("IPMI script timeout(" + cmd.toString() + "), will retry " + retry + " times"); + try { + TimeUnit.SECONDS.sleep(1); + } catch (InterruptedException e) { + s_logger.debug("[ignored] interupted while waiting to retry running script."); + } + continue; + } else if (res == null) { + return true; + } else { + break; + } + } + + s_logger.warn("IPMI Scirpt failed due to " + res + "(" + cmd.toString() + ")"); + return false; + } + + @Override + public boolean start() { + return true; + } + + @Override + public boolean stop() { + return true; + } + + @Override + public Type getType() { + return com.cloud.host.Host.Type.Routing; + } + + protected Map getHostVmStateReport() { + Map states = new HashMap(); + if (hostId != null) { + final List vms = vmDao.listByHostId(hostId); + for (VMInstanceVO vm : vms) { + states.put( + vm.getInstanceName(), + new HostVmStateReportEntry( + vm.getPowerState(), "host-" + hostId + ) + ); + } + } + return states; + } + + @Override + public StartupCommand[] initialize() { + StartupRoutingCommand cmd = new StartupRoutingCommand(0, 0, 0, 0, null, Hypervisor.HypervisorType.BareMetal, + new HashMap()); + + cmd.setDataCenter(_zone); + cmd.setPod(_pod); + cmd.setCluster(_cluster); + cmd.setGuid(_uuid); + cmd.setName(_ip); + cmd.setPrivateIpAddress(_ip); + cmd.setStorageIpAddress(_ip); + cmd.setVersion(BareMetalResourceBase.class.getPackage().getImplementationVersion()); + cmd.setCpus((int) _cpuNum); + cmd.setSpeed(_cpuCapacity); + cmd.setMemory(_memCapacity); + cmd.setPrivateMacAddress(_mac); + cmd.setPublicMacAddress(_mac); + return new StartupCommand[] { cmd }; + } + + private boolean ipmiPing() { + return doScript(_pingCommand); + } + + @Override + public PingCommand getCurrentStatus(long id) { + try { + if (!ipmiPing()) { + Thread.sleep(1000); + if (!ipmiPing()) { + s_logger.warn("Cannot ping ipmi nic " + _ip); + return null; + } + } + } catch (Exception e) { + s_logger.debug("Cannot ping ipmi nic " + _ip, e); + return null; + } + + return new PingRoutingCommand(getType(), id, null); + + /* + if (hostId != null) { + final List vms = vmDao.listByHostId(hostId); + if (vms.isEmpty()) { + return new PingRoutingCommand(getType(), id, null); + } else { + VMInstanceVO vm = vms.get(0); + SecurityGroupHttpClient client = new SecurityGroupHttpClient(); + HashMap> nwGrpStates = client.sync(vm.getInstanceName(), vm.getId(), vm.getPrivateIpAddress()); + return new PingRoutingWithNwGroupsCommand(getType(), id, null, nwGrpStates); + } + } else { + return new PingRoutingCommand(getType(), id, null); + } + */ + } + + protected Answer execute(IpmISetBootDevCommand cmd) { + Script bootCmd = null; + if (cmd.getBootDev() == BootDev.disk) { + bootCmd = _setDiskBootCommand; + } else if (cmd.getBootDev() == BootDev.pxe) { + bootCmd = _setPxeBootCommand; + } else { + throw new CloudRuntimeException("Unkonwn boot dev " + cmd.getBootDev()); + } + + String bootDev = cmd.getBootDev().name(); + if (!doScript(bootCmd)) { + s_logger.warn("Set " + _ip + " boot dev to " + bootDev + "failed"); + return new Answer(cmd, false, "Set " + _ip + " boot dev to " + bootDev + "failed"); + } + + s_logger.warn("Set " + _ip + " boot dev to " + bootDev + "Success"); + return new Answer(cmd, true, "Set " + _ip + " boot dev to " + bootDev + "Success"); + } + + protected MaintainAnswer execute(MaintainCommand cmd) { + return new MaintainAnswer(cmd, false); + } + + protected PrepareForMigrationAnswer execute(PrepareForMigrationCommand cmd) { + return new PrepareForMigrationAnswer(cmd); + } + + protected MigrateAnswer execute(MigrateCommand cmd) { + if (!doScript(_powerOffCommand)) { + return new MigrateAnswer(cmd, false, "IPMI power off failed", null); + } + return new MigrateAnswer(cmd, true, "success", null); + } + + protected CheckVirtualMachineAnswer execute(final CheckVirtualMachineCommand cmd) { + return new CheckVirtualMachineAnswer(cmd, PowerState.PowerOff, null); + } + + protected Answer execute(IpmiBootorResetCommand cmd) { + if (!doScript(_bootOrRebootCommand)) { + return new Answer(cmd, false, "IPMI boot or reboot failed"); + } + return new Answer(cmd, true, "Success"); + + } + + protected CheckNetworkAnswer execute(CheckNetworkCommand cmd) { + return new CheckNetworkAnswer(cmd, true, "Success"); + } + + protected Answer execute(SecurityGroupRulesCmd cmd) { + SecurityGroupHttpClient hc = new SecurityGroupHttpClient(); + return hc.call(cmd.getGuestIp(), cmd); + } + + @Override + public Answer executeRequest(Command cmd) { + try { + if (cmd instanceof ReadyCommand) { + return execute((ReadyCommand) cmd); + } else if (cmd instanceof StartCommand) { + return execute((StartCommand) cmd); + } else if (cmd instanceof StopCommand) { + return execute((StopCommand) cmd); + } else if (cmd instanceof RebootCommand) { + return execute((RebootCommand) cmd); + } else if (cmd instanceof IpmISetBootDevCommand) { + return execute((IpmISetBootDevCommand) cmd); + } else if (cmd instanceof MaintainCommand) { + return execute((MaintainCommand) cmd); + } else if (cmd instanceof PrepareForMigrationCommand) { + return execute((PrepareForMigrationCommand) cmd); + } else if (cmd instanceof MigrateCommand) { + return execute((MigrateCommand) cmd); + } else if (cmd instanceof CheckVirtualMachineCommand) { + return execute((CheckVirtualMachineCommand) cmd); + } else if (cmd instanceof IpmiBootorResetCommand) { + return execute((IpmiBootorResetCommand) cmd); + } else if (cmd instanceof SecurityGroupRulesCmd) { + return execute((SecurityGroupRulesCmd) cmd); + } else if (cmd instanceof CheckNetworkCommand) { + return execute((CheckNetworkCommand) cmd); + } else { + return Answer.createUnsupportedCommandAnswer(cmd); + } + } catch (Throwable t) { + s_logger.debug(t.getMessage(), t); + return new Answer(cmd, false, t.getMessage()); + } + } + + protected boolean isPowerOn(String str) { + if (str.startsWith("Chassis Power is on")) { + return true; + } else if (str.startsWith("Chassis Power is off")) { + return false; + } else { + throw new CloudRuntimeException("Cannot parse IPMI power status " + str); + } + } + + protected RebootAnswer execute(final RebootCommand cmd) { + String infoStr = "Command not supported in present state"; + OutputInterpreter.AllLinesParser interpreter = new OutputInterpreter.AllLinesParser(); + if (!doScript(_rebootCommand, interpreter, 10)) { + if (interpreter.getLines().contains(infoStr)) { + // try again, this error should be temporary + if (!doScript(_rebootCommand, interpreter, 10)) { + return new RebootAnswer(cmd, "IPMI reboot failed", false); + } + } else { + return new RebootAnswer(cmd, "IPMI reboot failed", false); + } + } + + return new RebootAnswer(cmd, "reboot succeeded", true); + } + + protected StopAnswer execute(final StopCommand cmd) { + boolean success = false; + int count = 0; + Script powerOff = _powerOffCommand; + + while (count < 10) { + if (!doScript(powerOff)) { + break; + } + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + break; + } + + OutputInterpreter.AllLinesParser interpreter = new OutputInterpreter.AllLinesParser(); + if (!doScript(_getStatusCommand, interpreter)) { + success = true; + s_logger.warn("Cannot get power status of " + getName() + ", assume VM state changed successfully"); + break; + } + + if (!isPowerOn(interpreter.getLines())) { + success = true; + break; + } else { + powerOff = _forcePowerOffCommand; + } + + count++; + } + + return success ? new StopAnswer(cmd, "Success", true) : new StopAnswer(cmd, "IPMI power off failed", false); + } + + protected StartAnswer execute(StartCommand cmd) { + VirtualMachineTO vm = cmd.getVirtualMachine(); + + OutputInterpreter.AllLinesParser interpreter = new OutputInterpreter.AllLinesParser(); + if (!doScript(_getStatusCommand, interpreter)) { + return new StartAnswer(cmd, "Cannot get current power status of " + getName()); + } + + if (isPowerOn(interpreter.getLines())) { + if (!doScript(_rebootCommand)) { + return new StartAnswer(cmd, "IPMI reboot failed"); + } + } else { + if (!doScript(_powerOnCommand)) { + return new StartAnswer(cmd, "IPMI power on failed"); + } + } + + if (_isEchoScAgent) { + SecurityGroupHttpClient hc = new SecurityGroupHttpClient(); + boolean echoRet = hc.echo(vm.getNics()[0].getIp(), TimeUnit.MINUTES.toMillis(30), TimeUnit.MINUTES.toMillis(1)); + if (!echoRet) { + return new StartAnswer(cmd, String.format("Call security group agent on vm[%s] timeout", vm.getNics()[0].getIp())); + } + } + + if (provisionDoneNotificationOn) { + QueryBuilder q = QueryBuilder.create(VMInstanceVO.class); + q.and(q.entity().getInstanceName(), SearchCriteria.Op.EQ, vm.getName()); + VMInstanceVO vmvo = q.find(); + + if (vmvo.getLastHostId() == null) { + // this is new created vm + long timeout = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(isProvisionDoneNotificationTimeout); + while (timeout > System.currentTimeMillis()) { + try { + TimeUnit.SECONDS.sleep(5); + } catch (InterruptedException e) { + s_logger.warn(e.getMessage(), e); + } + + q = QueryBuilder.create(VMInstanceVO.class); + q.and(q.entity().getInstanceName(), SearchCriteria.Op.EQ, vm.getName()); + vmvo = q.find(); + if (vmvo == null) { + return new StartAnswer(cmd, String.format("cannot find vm[name:%s] while waiting for baremtal provision done notification", vm.getName())); + } + + if (VirtualMachine.State.Running == vmvo.getState()) { + return new StartAnswer(cmd); + } + + s_logger.debug(String.format("still wait for baremetal provision done notification for vm[name:%s], current vm state is %s", vmvo.getInstanceName(), vmvo.getState())); + } + + return new StartAnswer(cmd, String.format("timeout after %s seconds, no baremetal provision done notification received. vm[name:%s] failed to start", isProvisionDoneNotificationTimeout, vm.getName())); + } + } + + s_logger.debug("Start bare metal vm " + vm.getName() + "successfully"); + _vmName = vm.getName(); + return new StartAnswer(cmd); + } + + protected ReadyAnswer execute(ReadyCommand cmd) { + // derived resource should check if the PXE server is ready + s_logger.debug("Bare metal resource " + getName() + " is ready"); + return new ReadyAnswer(cmd); + } + + @Override + public void disconnected() { + + } + + @Override + public IAgentControl getAgentControl() { + return _agentControl; + } + + @Override + public void setAgentControl(IAgentControl agentControl) { + _agentControl = agentControl; + } + +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetaNetworkGuru.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetaNetworkGuru.java new file mode 100644 index 0000000000..ff5037fe90 --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetaNetworkGuru.java @@ -0,0 +1,174 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +package com.cloud.baremetal.networkservice; + +import javax.inject.Inject; + +import com.cloud.dc.DataCenter; +import com.cloud.dc.Pod; +import com.cloud.dc.PodVlanMapVO; +import com.cloud.dc.Vlan; +import com.cloud.dc.Vlan.VlanType; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.dc.dao.PodVlanMapDao; +import com.cloud.dc.dao.VlanDao; +import com.cloud.deploy.DeployDestination; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientAddressCapacityException; +import com.cloud.exception.InsufficientVirtualNetworkCapacityException; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.network.IpAddressManager; +import com.cloud.network.Network; +import com.cloud.network.Networks.AddressFormat; +import com.cloud.network.Networks.BroadcastDomainType; +import com.cloud.network.Networks.IsolationType; +import com.cloud.network.addr.PublicIp; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.dao.IPAddressVO; +import com.cloud.network.guru.DirectPodBasedNetworkGuru; +import com.cloud.offerings.dao.NetworkOfferingDao; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallbackNoReturn; +import com.cloud.utils.db.TransactionStatus; +import com.cloud.vm.NicProfile; +import com.cloud.vm.ReservationContext; +import com.cloud.vm.VirtualMachineProfile; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BaremetaNetworkGuru extends DirectPodBasedNetworkGuru { + private static final Logger s_logger = LoggerFactory.getLogger(BaremetaNetworkGuru.class); + @Inject + private HostDao _hostDao; + @Inject + DataCenterDao _dcDao; + @Inject + VlanDao _vlanDao; + @Inject + NetworkOrchestrationService _networkMgr; + @Inject + IPAddressDao _ipAddressDao; + @Inject + NetworkOfferingDao _networkOfferingDao; + @Inject + PodVlanMapDao _podVlanDao; + @Inject + IpAddressManager _ipAddrMgr; + + @Override + public void reserve(NicProfile nic, Network network, VirtualMachineProfile vm, DeployDestination dest, ReservationContext context) + throws InsufficientAddressCapacityException, ConcurrentOperationException, InsufficientVirtualNetworkCapacityException { + if (dest.getHost().getHypervisorType() != HypervisorType.BareMetal) { + super.reserve(nic, network, vm, dest, context); + return; + } + + HostVO host = _hostDao.findById(dest.getHost().getId()); + _hostDao.loadDetails(host); + String intentIp = host.getDetail(ApiConstants.IP_ADDRESS); + if (intentIp == null) { + super.reserve(nic, network, vm, dest, context); + return; + } + + String oldIp = nic.getIPv4Address(); + boolean getNewIp = false; + if (oldIp == null) { + getNewIp = true; + } else { + // we need to get a new ip address if we try to deploy a vm in a + // different pod + final IPAddressVO ipVO = _ipAddressDao.findByIpAndSourceNetworkId(network.getId(), oldIp); + if (ipVO != null) { + PodVlanMapVO mapVO = _podVlanDao.listPodVlanMapsByVlan(ipVO.getVlanId()); + if (mapVO.getPodId() != dest.getPod().getId()) { + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + // release the old ip here + _ipAddrMgr.markIpAsUnavailable(ipVO.getId()); + _ipAddressDao.unassignIpAddress(ipVO.getId()); + } + }); + + nic.setIPv4Address(null); + getNewIp = true; + } + } + } + + if (getNewIp) { + // we don't set reservationStrategy to Create because we need this + // method to be called again for the case when vm fails to deploy in + // Pod1, and we try to redeploy it in Pod2 + getBaremetalIp(nic, dest.getPod(), vm, network, intentIp); + } + + DataCenter dc = _dcDao.findById(network.getDataCenterId()); + nic.setIPv4Dns1(dc.getDns1()); + nic.setIPv4Dns2(dc.getDns2()); + + /* + * Pod pod = dest.getPod(); Pair ip = + * _dcDao.allocatePrivateIpAddress(dest.getDataCenter().getId(), + * dest.getPod().getId(), nic.getId(), context.getReservationId(), + * intentIp); if (ip == null) { throw new + * InsufficientAddressCapacityException + * ("Unable to get a management ip address", Pod.class, pod.getId()); } + * + * nic.setIp4Address(ip.first()); + * nic.setMacAddress(NetUtils.long2Mac(NetUtils + * .createSequenceBasedMacAddress(ip.second()))); + * nic.setGateway(pod.getGateway()); nic.setFormat(AddressFormat.Ip4); + * String netmask = NetUtils.getCidrNetmask(pod.getCidrSize()); + * nic.setNetmask(netmask); + * nic.setBroadcastType(BroadcastDomainType.Native); + * nic.setBroadcastUri(null); nic.setIsolationUri(null); + */ + + s_logger.debug("Allocated a nic " + nic + " for " + vm); + } + + private void getBaremetalIp(NicProfile nic, Pod pod, VirtualMachineProfile vm, Network network, String requiredIp) throws + InsufficientAddressCapacityException, ConcurrentOperationException { + DataCenter dc = _dcDao.findById(pod.getDataCenterId()); + if (nic.getIPv4Address() == null) { + s_logger.debug(String.format("Requiring ip address: %s", nic.getIPv4Address())); + PublicIp ip = _ipAddrMgr.assignPublicIpAddress(dc.getId(), pod.getId(), vm.getOwner(), VlanType.DirectAttached, network.getId(), requiredIp, false); + nic.setIPv4Address(ip.getAddress().toString()); + nic.setFormat(AddressFormat.Ip4); + nic.setIPv4Gateway(ip.getGateway()); + nic.setIPv4Netmask(ip.getNetmask()); + if (ip.getVlanTag() != null && ip.getVlanTag().equalsIgnoreCase(Vlan.UNTAGGED)) { + nic.setIsolationUri(IsolationType.Ec2.toUri(Vlan.UNTAGGED)); + nic.setBroadcastUri(BroadcastDomainType.Vlan.toUri(Vlan.UNTAGGED)); + nic.setBroadcastType(BroadcastDomainType.Native); + } + nic.setReservationId(String.valueOf(ip.getVlanTag())); + nic.setMacAddress(ip.getMacAddress()); + } + nic.setIPv4Dns1(dc.getDns1()); + nic.setIPv4Dns2(dc.getDns2()); + } +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpElement.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpElement.java new file mode 100644 index 0000000000..1664e207ee --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpElement.java @@ -0,0 +1,185 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +package com.cloud.baremetal.networkservice; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import javax.inject.Inject; + +import com.cloud.baremetal.database.BaremetalDhcpVO; +import com.cloud.dc.DataCenter.NetworkType; +import com.cloud.dc.Pod; +import com.cloud.deploy.DeployDestination; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.IllegalVirtualMachineException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.host.Host; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.network.Network; +import com.cloud.network.Network.Capability; +import com.cloud.network.Network.GuestType; +import com.cloud.network.Network.Provider; +import com.cloud.network.Network.Service; +import com.cloud.network.Networks.TrafficType; +import com.cloud.network.PhysicalNetworkServiceProvider; +import com.cloud.network.element.DhcpServiceProvider; +import com.cloud.offering.NetworkOffering; +import com.cloud.utils.component.AdapterBase; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.QueryBuilder; +import com.cloud.utils.db.SearchCriteria.Op; +import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; +import com.cloud.vm.ReservationContext; +import com.cloud.vm.VirtualMachine.Type; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.NicDao; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BaremetalDhcpElement extends AdapterBase implements DhcpServiceProvider { + private static final Logger s_logger = LoggerFactory.getLogger(BaremetalDhcpElement.class); + private static final Map> capabilities; + + @Inject + NicDao _nicDao; + @Inject + BaremetalDhcpManager _dhcpMgr; + + static { + Capability cap = new Capability(BaremetalDhcpManager.BAREMETAL_DHCP_SERVICE_CAPABITLITY); + Map baremetalCaps = new HashMap(); + baremetalCaps.put(cap, null); + baremetalCaps.put(Capability.DhcpAccrossMultipleSubnets, Boolean.TRUE.toString()); + capabilities = new HashMap>(); + capabilities.put(Service.Dhcp, baremetalCaps); + } + + @Override + public Map> getCapabilities() { + return capabilities; + } + + @Override + public Provider getProvider() { + return BaremetalDhcpManager.BAREMETAL_DHCP_SERVICE_PROVIDER; + } + + private boolean canHandle(DeployDestination dest, TrafficType trafficType, GuestType networkType) { + Pod pod = dest.getPod(); + if (pod != null && dest.getDataCenter().getNetworkType() == NetworkType.Basic && trafficType == TrafficType.Guest) { + QueryBuilder sc = QueryBuilder.create(BaremetalDhcpVO.class); + sc.and(sc.entity().getPodId(), Op.EQ, pod.getId()); + return sc.find() != null; + } + + return false; + } + + @Override + public boolean implement(Network network, NetworkOffering offering, DeployDestination dest, ReservationContext context) throws ConcurrentOperationException, + ResourceUnavailableException, InsufficientCapacityException { + if (offering.isSystemOnly() || !canHandle(dest, offering.getTrafficType(), network.getGuestType())) { + s_logger.debug("BaremetalDhcpElement can not handle networkoffering: " + offering.getName()); + return false; + } + return true; + } + + @Override + @DB + public boolean prepare(Network network, NicProfile nic, VirtualMachineProfile vm, DeployDestination dest, ReservationContext context) + throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException, IllegalVirtualMachineException { + Host host = dest.getHost(); + if (vm.getType() != Type.User || vm.getHypervisorType() != HypervisorType.BareMetal) { + return false; + } + + nic.setMacAddress(host.getPrivateMacAddress()); + NicVO vo = _nicDao.findById(nic.getId()); + assert vo != null : "Where ths nic " + nic.getId() + " going???"; + vo.setMacAddress(nic.getMacAddress()); + _nicDao.update(vo.getId(), vo); + return true; + } + + @Override + public boolean release(Network network, NicProfile nic, VirtualMachineProfile vm, ReservationContext context) throws ConcurrentOperationException, + ResourceUnavailableException { + return true; + } + + @Override + public boolean shutdown(Network network, ReservationContext context, boolean cleanup) throws ConcurrentOperationException, ResourceUnavailableException { + return true; + } + + @Override + public boolean destroy(Network network, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException { + return true; + } + + @Override + public boolean isReady(PhysicalNetworkServiceProvider provider) { + return true; + } + + @Override + public boolean shutdownProviderInstances(PhysicalNetworkServiceProvider provider, ReservationContext context) throws ConcurrentOperationException, + ResourceUnavailableException { + return true; + } + + @Override + public boolean canEnableIndividualServices() { + return false; + } + + @Override + public boolean verifyServicesCombination(Set services) { + return true; + } + + @Override + public boolean addDhcpEntry(Network network, NicProfile nic, VirtualMachineProfile vm, DeployDestination dest, ReservationContext context) + throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException { + if (vm.getHypervisorType() != HypervisorType.BareMetal || !canHandle(dest, network.getTrafficType(), network.getGuestType())) { + return false; + } + return _dhcpMgr.addVirtualMachineIntoNetwork(network, nic, vm, dest, context); + } + + @Override + public boolean configDhcpSupportForSubnet(Network network, NicProfile nic, VirtualMachineProfile vm, DeployDestination dest, ReservationContext context) + throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException { + //TODO Add support for baremetal + return true; + } + + @Override + public boolean removeDhcpSupportForSubnet(Network network) { + //TODO Add support for baremetal + return true; + } + +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpManager.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpManager.java new file mode 100644 index 0000000000..e2b03a4ace --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpManager.java @@ -0,0 +1,58 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +// Apache License, Version 2.0 (the "License"); you may not use this +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// Automatically generated by addcopyright.py at 04/03/2012 +package com.cloud.baremetal.networkservice; + +import java.util.List; + +import com.cloud.baremetal.database.BaremetalDhcpVO; +import com.cloud.deploy.DeployDestination; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Network; +import com.cloud.network.Network.Provider; +import com.cloud.utils.component.Manager; +import com.cloud.utils.component.PluggableService; +import com.cloud.vm.NicProfile; +import com.cloud.vm.ReservationContext; +import com.cloud.vm.VirtualMachineProfile; + +import org.apache.cloudstack.api.AddBaremetalDhcpCmd; +import org.apache.cloudstack.api.ListBaremetalDhcpCmd; + +public interface BaremetalDhcpManager extends Manager, PluggableService { + public static enum BaremetalDhcpType { + DNSMASQ, DHCPD, + } + + boolean addVirtualMachineIntoNetwork(Network network, NicProfile nic, VirtualMachineProfile profile, DeployDestination dest, ReservationContext context) + throws ResourceUnavailableException; + + BaremetalDhcpVO addDchpServer(AddBaremetalDhcpCmd cmd); + + BaremetalDhcpResponse generateApiResponse(BaremetalDhcpVO vo); + + List listBaremetalDhcps(ListBaremetalDhcpCmd cmd); + + public static final String BAREMETAL_DHCP_SERVICE_CAPABITLITY = "BaremetalDhcp"; + public static final String BAREMETAL_DHCP_SERVICE_PROPERTIES = "baremetaldhcp_commands.properties"; + public static final Provider BAREMETAL_DHCP_SERVICE_PROVIDER = new Provider("BaremetalDhcpProvider", true); +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpManagerImpl.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpManagerImpl.java new file mode 100644 index 0000000000..59d77036be --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpManagerImpl.java @@ -0,0 +1,326 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +// Apache License, Version 2.0 (the "License"); you may not use this +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// Automatically generated by addcopyright.py at 04/03/2012 +package com.cloud.baremetal.networkservice; + +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.StartupCommand; +import com.cloud.agent.api.StartupExternalDhcpCommand; +import com.cloud.agent.api.routing.DhcpEntryCommand; +import com.cloud.baremetal.database.BaremetalDhcpDao; +import com.cloud.baremetal.database.BaremetalDhcpVO; +import com.cloud.dc.DataCenter; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.dc.dao.HostPodDao; +import com.cloud.deploy.DeployDestination; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.host.Host; +import com.cloud.host.Host.Type; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.network.Network; +import com.cloud.network.NetworkModel; +import com.cloud.network.PhysicalNetworkServiceProvider; +import com.cloud.network.dao.PhysicalNetworkDao; +import com.cloud.network.dao.PhysicalNetworkServiceProviderDao; +import com.cloud.network.dao.PhysicalNetworkServiceProviderVO; +import com.cloud.network.dao.PhysicalNetworkVO; +import com.cloud.resource.ResourceManager; +import com.cloud.resource.ResourceStateAdapter; +import com.cloud.resource.ServerResource; +import com.cloud.resource.UnableDeleteHostException; +import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.QueryBuilder; +import com.cloud.utils.db.SearchCriteria.Op; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.NicProfile; +import com.cloud.vm.ReservationContext; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.UserVmDao; + +import org.apache.cloudstack.api.AddBaremetalDhcpCmd; +import org.apache.cloudstack.api.ListBaremetalDhcpCmd; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BaremetalDhcpManagerImpl extends ManagerBase implements BaremetalDhcpManager, ResourceStateAdapter { + private static final Logger s_logger = LoggerFactory.getLogger(BaremetalDhcpManagerImpl.class); + protected String _name; + @Inject + DataCenterDao _dcDao; + @Inject + HostDao _hostDao; + @Inject + AgentManager _agentMgr; + @Inject + HostPodDao _podDao; + @Inject + UserVmDao _userVmDao; + @Inject + ResourceManager _resourceMgr; + @Inject + NicDao _nicDao; + @Inject + PhysicalNetworkDao _physicalNetworkDao; + @Inject + PhysicalNetworkServiceProviderDao _physicalNetworkServiceProviderDao; + @Inject + BaremetalDhcpDao _extDhcpDao; + @Inject + NetworkModel _ntwkModel; + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + _resourceMgr.registerResourceStateAdapter(this.getClass().getSimpleName(), this); + return true; + } + + @Override + public boolean start() { + return true; + } + + @Override + public boolean stop() { + _resourceMgr.unregisterResourceStateAdapter(this.getClass().getSimpleName()); + return true; + } + + @Override + public String getName() { + return _name; + } + + protected String getDhcpServerGuid(String zoneId, String name, String ip) { + return zoneId + "-" + name + "-" + ip; + } + + @Override + public boolean addVirtualMachineIntoNetwork(Network network, NicProfile nic, VirtualMachineProfile profile, DeployDestination dest, ReservationContext context) + throws ResourceUnavailableException { + Long zoneId = profile.getVirtualMachine().getDataCenterId(); + Long podId = profile.getVirtualMachine().getPodIdToDeployIn(); + List hosts = _resourceMgr.listAllUpAndEnabledHosts(Type.BaremetalDhcp, null, podId, zoneId); + if (hosts.size() == 0) { + throw new CloudRuntimeException("No external Dhcp found in zone " + zoneId + " pod " + podId); + } + + if (hosts.size() > 1) { + throw new CloudRuntimeException("Something wrong, more than 1 external Dhcp found in zone " + zoneId + " pod " + podId); + } + + HostVO h = hosts.get(0); + String dns = nic.getIPv4Dns1(); + if (dns == null) { + dns = nic.getIPv4Dns2(); + } + DhcpEntryCommand dhcpCommand = + new DhcpEntryCommand(nic.getMacAddress(), nic.getIPv4Address(), profile.getVirtualMachine().getHostName(), null, dns, nic.getIPv4Gateway(), null, + _ntwkModel.getExecuteInSeqNtwkElmtCmd()); + String errMsg = + String.format("Set dhcp entry on external DHCP %1$s failed(ip=%2$s, mac=%3$s, vmname=%4$s)", h.getPrivateIpAddress(), nic.getIPv4Address(), + nic.getMacAddress(), profile.getVirtualMachine().getHostName()); + // prepareBareMetalDhcpEntry(nic, dhcpCommand); + try { + Answer ans = _agentMgr.send(h.getId(), dhcpCommand); + if (ans.getResult()) { + s_logger.debug(String.format("Set dhcp entry on external DHCP %1$s successfully(ip=%2$s, mac=%3$s, vmname=%4$s)", h.getPrivateIpAddress(), + nic.getIPv4Address(), nic.getMacAddress(), profile.getVirtualMachine().getHostName())); + return true; + } else { + s_logger.debug(errMsg + " " + ans.getDetails()); + throw new ResourceUnavailableException(errMsg, DataCenter.class, zoneId); + } + } catch (Exception e) { + s_logger.debug(errMsg, e); + throw new ResourceUnavailableException(errMsg + e.getMessage(), DataCenter.class, zoneId); + } + } + + @Override + public HostVO createHostVOForConnectedAgent(HostVO host, StartupCommand[] cmd) { + // TODO Auto-generated method stub + return null; + } + + @Override + public HostVO createHostVOForDirectConnectAgent(HostVO host, StartupCommand[] startup, ServerResource resource, Map details, List hostTags) { + if (!(startup[0] instanceof StartupExternalDhcpCommand)) { + return null; + } + + host.setType(Host.Type.BaremetalDhcp); + return host; + } + + @Override + public DeleteHostAnswer deleteHost(HostVO host, boolean isForced, boolean isForceDeleteStorage) throws UnableDeleteHostException { + // TODO Auto-generated method stub + return null; + } + + @Override + @DB + public BaremetalDhcpVO addDchpServer(AddBaremetalDhcpCmd cmd) { + PhysicalNetworkVO pNetwork = null; + long zoneId; + + if (cmd.getPhysicalNetworkId() == null || cmd.getUrl() == null || cmd.getUsername() == null || cmd.getPassword() == null) { + throw new IllegalArgumentException("At least one of the required parameters(physical network id, url, username, password) is null"); + } + + pNetwork = _physicalNetworkDao.findById(cmd.getPhysicalNetworkId()); + if (pNetwork == null) { + throw new IllegalArgumentException("Could not find phyical network with ID: " + cmd.getPhysicalNetworkId()); + } + zoneId = pNetwork.getDataCenterId(); + DataCenterVO zone = _dcDao.findById(zoneId); + + PhysicalNetworkServiceProviderVO ntwkSvcProvider = + _physicalNetworkServiceProviderDao.findByServiceProvider(pNetwork.getId(), BaremetalDhcpManager.BAREMETAL_DHCP_SERVICE_PROVIDER.getName()); + if (ntwkSvcProvider == null) { + throw new CloudRuntimeException("Network Service Provider: " + BaremetalDhcpManager.BAREMETAL_DHCP_SERVICE_PROVIDER.getName() + + " is not enabled in the physical network: " + cmd.getPhysicalNetworkId() + "to add this device"); + } else if (ntwkSvcProvider.getState() == PhysicalNetworkServiceProvider.State.Shutdown) { + throw new CloudRuntimeException("Network Service Provider: " + ntwkSvcProvider.getProviderName() + " is in shutdown state in the physical network: " + + cmd.getPhysicalNetworkId() + "to add this device"); + } + + List dhcps = _resourceMgr.listAllUpAndEnabledHosts(Host.Type.BaremetalDhcp, null, null, zoneId); + if (dhcps.size() != 0) { + throw new IllegalArgumentException("Already had a DHCP server in zone: " + zoneId); + } + + URI uri; + try { + uri = new URI(cmd.getUrl()); + } catch (Exception e) { + s_logger.debug(e.toString()); + throw new IllegalArgumentException(e.getMessage()); + } + + String ipAddress = uri.getHost(); + if (ipAddress == null) { + ipAddress = cmd.getUrl(); // the url is raw ip. For backforward compatibility, we have to support http://ip format as well + } + String guid = getDhcpServerGuid(Long.toString(zoneId), "ExternalDhcp", ipAddress); + Map params = new HashMap(); + params.put("type", cmd.getDhcpType()); + params.put("zone", Long.toString(zoneId)); + params.put("ip", ipAddress); + params.put("username", cmd.getUsername()); + params.put("password", cmd.getPassword()); + params.put("guid", guid); + String dns = zone.getDns1(); + if (dns == null) { + dns = zone.getDns2(); + } + params.put("dns", dns); + + ServerResource resource = null; + try { + if (cmd.getDhcpType().equalsIgnoreCase(BaremetalDhcpType.DNSMASQ.toString())) { + resource = new BaremetalDnsmasqResource(); + resource.configure("Dnsmasq resource", params); + } else if (cmd.getDhcpType().equalsIgnoreCase(BaremetalDhcpType.DHCPD.toString())) { + resource = new BaremetalDhcpdResource(); + resource.configure("Dhcpd resource", params); + } else { + throw new CloudRuntimeException("Unsupport DHCP server type: " + cmd.getDhcpType()); + } + } catch (Exception e) { + s_logger.debug(e.toString()); + throw new CloudRuntimeException(e.getMessage()); + } + + Host dhcpServer = _resourceMgr.addHost(zoneId, resource, Host.Type.BaremetalDhcp, params); + if (dhcpServer == null) { + throw new CloudRuntimeException("Cannot add external Dhcp server as a host"); + } + + BaremetalDhcpVO vo = new BaremetalDhcpVO(); + vo.setDeviceType(cmd.getDhcpType()); + vo.setHostId(dhcpServer.getId()); + vo.setNetworkServiceProviderId(ntwkSvcProvider.getId()); + vo.setPhysicalNetworkId(cmd.getPhysicalNetworkId()); + _extDhcpDao.persist(vo); + return vo; + } + + @Override + public BaremetalDhcpResponse generateApiResponse(BaremetalDhcpVO vo) { + BaremetalDhcpResponse response = new BaremetalDhcpResponse(); + response.setDeviceType(vo.getDeviceType()); + response.setId(vo.getUuid()); + HostVO host = _hostDao.findById(vo.getHostId()); + response.setUrl(host.getPrivateIpAddress()); + PhysicalNetworkVO nwVO = _physicalNetworkDao.findById(vo.getPhysicalNetworkId()); + response.setPhysicalNetworkId(nwVO.getUuid()); + PhysicalNetworkServiceProviderVO providerVO = _physicalNetworkServiceProviderDao.findById(vo.getNetworkServiceProviderId()); + response.setProviderId(providerVO.getUuid()); + response.setObjectName("baremetaldhcp"); + return response; + } + + @Override + public List listBaremetalDhcps(ListBaremetalDhcpCmd cmd) { + List responses = new ArrayList(); + if (cmd.getId() != null) { + BaremetalDhcpVO vo = _extDhcpDao.findById(cmd.getId()); + responses.add(generateApiResponse(vo)); + return responses; + } + + QueryBuilder sc = QueryBuilder.create(BaremetalDhcpVO.class); + if (cmd.getDeviceType() != null) { + sc.and(sc.entity().getDeviceType(), Op.EQ, cmd.getDeviceType()); + } + + sc.and(sc.entity().getPhysicalNetworkId(), Op.EQ, cmd.getPhysicalNetworkId()); + List vos = sc.list(); + for (BaremetalDhcpVO vo : vos) { + responses.add(generateApiResponse(vo)); + } + return responses; + } + + @Override + public List> getCommands() { + List> cmds = new ArrayList>(); + cmds.add(AddBaremetalDhcpCmd.class); + cmds.add(ListBaremetalDhcpCmd.class); + return cmds; + } +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpResourceBase.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpResourceBase.java new file mode 100644 index 0000000000..2beafdf613 --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpResourceBase.java @@ -0,0 +1,159 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +// Apache License, Version 2.0 (the "License"); you may not use this +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// Automatically generated by addcopyright.py at 04/03/2012 +package com.cloud.baremetal.networkservice; + +import java.util.HashMap; +import java.util.Map; + +import javax.naming.ConfigurationException; + +import com.cloud.agent.IAgentControl; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.HostVmStateReportEntry; +import com.cloud.agent.api.PingCommand; +import com.cloud.agent.api.PingRoutingCommand; +import com.cloud.agent.api.ReadyAnswer; +import com.cloud.agent.api.ReadyCommand; +import com.cloud.agent.api.StartupCommand; +import com.cloud.agent.api.StartupExternalDhcpCommand; +import com.cloud.host.Host.Type; +import com.cloud.resource.ServerResource; +import com.cloud.utils.component.ManagerBase; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BaremetalDhcpResourceBase extends ManagerBase implements ServerResource { + private static final Logger s_logger = LoggerFactory.getLogger(BaremetalDhcpResourceBase.class); + String _name; + String _guid; + String _username; + String _password; + String _ip; + String _zoneId; + String _dns; + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + _name = name; + _guid = (String)params.get("guid"); + _ip = (String)params.get("ip"); + _username = (String)params.get("username"); + _password = (String)params.get("password"); + _zoneId = (String)params.get("zone"); + _dns = (String)params.get("dns"); + + if (_guid == null) { + throw new ConfigurationException("No Guid specified"); + } + + if (_zoneId == null) { + throw new ConfigurationException("No Zone specified"); + } + + if (_ip == null) { + throw new ConfigurationException("No IP specified"); + } + + if (_username == null) { + throw new ConfigurationException("No username specified"); + } + + if (_password == null) { + throw new ConfigurationException("No password specified"); + } + + if (_dns == null) { + throw new ConfigurationException("No dns specified"); + } + + return true; + } + + @Override + public boolean start() { + return true; + } + + @Override + public boolean stop() { + return true; + } + + @Override + public String getName() { + return _name; + } + + @Override + public Type getType() { + return Type.BaremetalDhcp; + } + + @Override + public StartupCommand[] initialize() { + StartupExternalDhcpCommand cmd = new StartupExternalDhcpCommand(); + cmd.setName(_name); + cmd.setDataCenter(_zoneId); + cmd.setPrivateIpAddress(_ip); + cmd.setStorageIpAddress(""); + cmd.setVersion(""); + cmd.setGuid(_guid); + return new StartupCommand[] {cmd}; + } + + @Override + public PingCommand getCurrentStatus(long id) { + //TODO: check server + return new PingRoutingCommand(getType(), id, new HashMap()); + } + + protected ReadyAnswer execute(ReadyCommand cmd) { + s_logger.debug("External DHCP resource " + _name + " is ready"); + return new ReadyAnswer(cmd); + } + + @Override + public Answer executeRequest(Command cmd) { + if (cmd instanceof ReadyCommand) { + return execute((ReadyCommand)cmd); + } else { + return Answer.createUnsupportedCommandAnswer(cmd); + } + } + + @Override + public void disconnected() { + } + + @Override + public IAgentControl getAgentControl() { + return null; + } + + @Override + public void setAgentControl(IAgentControl agentControl) { + } + +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpResponse.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpResponse.java new file mode 100644 index 0000000000..1ddcf0a885 --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpResponse.java @@ -0,0 +1,90 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +package com.cloud.baremetal.networkservice; + +import com.cloud.baremetal.database.BaremetalDhcpVO; +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; + +@EntityReference(value = BaremetalDhcpVO.class) +public class BaremetalDhcpResponse extends BaseResponse { + @SerializedName(ApiConstants.ID) + @Param(description = "device id of ") + private String id; + + @SerializedName(ApiConstants.PHYSICAL_NETWORK_ID) + @Param(description = "the physical network to which this external dhcp device belongs to") + private String physicalNetworkId; + + @SerializedName(ApiConstants.PROVIDER) + @Param(description = "name of the provider") + private String providerId; + + @SerializedName(ApiConstants.DHCP_SERVER_TYPE) + @Param(description = "name of the provider") + private String deviceType; + + @SerializedName(ApiConstants.URL) + @Param(description = "url") + private String url; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getPhysicalNetworkId() { + return physicalNetworkId; + } + + public void setPhysicalNetworkId(String physicalNetworkId) { + this.physicalNetworkId = physicalNetworkId; + } + + public String getProviderId() { + return providerId; + } + + public void setProviderId(String providerId) { + this.providerId = providerId; + } + + public String getDeviceType() { + return deviceType; + } + + public void setDeviceType(String deviceType) { + this.deviceType = deviceType; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpdResource.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpdResource.java new file mode 100644 index 0000000000..354369b3d1 --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpdResource.java @@ -0,0 +1,141 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +// Apache License, Version 2.0 (the "License"); you may not use this +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// Automatically generated by addcopyright.py at 04/03/2012 +package com.cloud.baremetal.networkservice; + +import java.util.HashMap; +import java.util.Map; + +import javax.naming.ConfigurationException; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.HostVmStateReportEntry; +import com.cloud.agent.api.PingCommand; +import com.cloud.agent.api.PingRoutingCommand; +import com.cloud.agent.api.routing.DhcpEntryCommand; +import com.cloud.utils.script.Script; +import com.cloud.utils.ssh.SSHCmdHelper; +import com.trilead.ssh2.SCPClient; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BaremetalDhcpdResource extends BaremetalDhcpResourceBase { + private static final Logger s_logger = LoggerFactory.getLogger(BaremetalDhcpdResource.class); + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + com.trilead.ssh2.Connection sshConnection = null; + try { + super.configure(name, params); + s_logger.debug(String.format("Trying to connect to DHCP server(IP=%1$s, username=%2$s, password=%3$s)", _ip, _username, "******")); + sshConnection = SSHCmdHelper.acquireAuthorizedConnection(_ip, _username, _password); + if (sshConnection == null) { + throw new ConfigurationException(String.format("Cannot connect to DHCP server(IP=%1$s, username=%2$s, password=%3$s", _ip, _username, "******")); + } + + if (!SSHCmdHelper.sshExecuteCmd(sshConnection, "[ -f '/usr/sbin/dhcpd' ]")) { + throw new ConfigurationException("Cannot find dhcpd.conf /etc/dhcpd.conf at on " + _ip); + } + + SCPClient scp = new SCPClient(sshConnection); + + String editHosts = "scripts/network/exdhcp/dhcpd_edithosts.py"; + String editHostsPath = Script.findScript("", editHosts); + if (editHostsPath == null) { + throw new ConfigurationException("Can not find script dnsmasq_edithosts.sh at " + editHosts); + } + scp.put(editHostsPath, "/usr/bin/", "0755"); + + String prepareDhcpdScript = "scripts/network/exdhcp/prepare_dhcpd.sh"; + String prepareDhcpdScriptPath = Script.findScript("", prepareDhcpdScript); + if (prepareDhcpdScriptPath == null) { + throw new ConfigurationException("Can not find prepare_dhcpd.sh at " + prepareDhcpdScriptPath); + } + scp.put(prepareDhcpdScriptPath, "/usr/bin/", "0755"); + + //TODO: tooooooooooooooo ugly here!!! + String[] ips = _ip.split("\\."); + ips[3] = "0"; + StringBuffer buf = new StringBuffer(); + int i; + for (i = 0; i < ips.length - 1; i++) { + buf.append(ips[i]).append("."); + } + buf.append(ips[i]); + String subnet = buf.toString(); + String cmd = String.format("sh /usr/bin/prepare_dhcpd.sh %1$s", subnet); + if (!SSHCmdHelper.sshExecuteCmd(sshConnection, cmd)) { + throw new ConfigurationException("prepare Dhcpd at " + _ip + " failed, command:" + cmd); + } + + s_logger.debug("Dhcpd resource configure successfully"); + return true; + } catch (Exception e) { + s_logger.debug("Dhcpd resource configure failed", e); + throw new ConfigurationException(e.getMessage()); + } finally { + SSHCmdHelper.releaseSshConnection(sshConnection); + } + } + + @Override + public PingCommand getCurrentStatus(long id) { + com.trilead.ssh2.Connection sshConnection = SSHCmdHelper.acquireAuthorizedConnection(_ip, _username, _password); + if (sshConnection == null) { + return null; + } else { + SSHCmdHelper.releaseSshConnection(sshConnection); + return new PingRoutingCommand(getType(), id, new HashMap()); + } + } + + Answer execute(DhcpEntryCommand cmd) { + com.trilead.ssh2.Connection sshConnection = null; + try { + sshConnection = SSHCmdHelper.acquireAuthorizedConnection(_ip, _username, _password); + if (sshConnection == null) { + return new Answer(cmd, false, "ssh authenticate failed"); + } + String addDhcp = + String.format("python /usr/bin/dhcpd_edithosts.py %1$s %2$s %3$s %4$s %5$s %6$s", cmd.getVmMac(), cmd.getVmIpAddress(), cmd.getVmName(), cmd.getDns(), + cmd.getGateway(), cmd.getNextServer()); + if (!SSHCmdHelper.sshExecuteCmd(sshConnection, addDhcp)) { + return new Answer(cmd, false, "add Dhcp entry failed"); + } else { + return new Answer(cmd); + } + } finally { + SSHCmdHelper.releaseSshConnection(sshConnection); + } + } + + @Override + public Answer executeRequest(Command cmd) { + if (cmd instanceof DhcpEntryCommand) { + return execute((DhcpEntryCommand)cmd); + } else { + return super.executeRequest(cmd); + } + } +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDnsmasqResource.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDnsmasqResource.java new file mode 100644 index 0000000000..2ccfb6db55 --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDnsmasqResource.java @@ -0,0 +1,131 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +// Apache License, Version 2.0 (the "License"); you may not use this +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// Automatically generated by addcopyright.py at 04/03/2012 +package com.cloud.baremetal.networkservice; + +import java.util.HashMap; +import java.util.Map; + +import javax.naming.ConfigurationException; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.HostVmStateReportEntry; +import com.cloud.agent.api.PingCommand; +import com.cloud.agent.api.PingRoutingCommand; +import com.cloud.agent.api.routing.DhcpEntryCommand; +import com.cloud.utils.script.Script; +import com.cloud.utils.ssh.SSHCmdHelper; +import com.trilead.ssh2.SCPClient; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BaremetalDnsmasqResource extends BaremetalDhcpResourceBase { + private static final Logger s_logger = LoggerFactory.getLogger(BaremetalDnsmasqResource.class); + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + com.trilead.ssh2.Connection sshConnection = null; + try { + super.configure(name, params); + s_logger.debug(String.format("Trying to connect to DHCP server(IP=%1$s, username=%2$s, password=%3$s)", _ip, _username, _password)); + sshConnection = SSHCmdHelper.acquireAuthorizedConnection(_ip, _username, _password); + if (sshConnection == null) { + throw new ConfigurationException(String.format("Cannot connect to DHCP server(IP=%1$s, username=%2$s, password=%3$s", _ip, _username, _password)); + } + + if (!SSHCmdHelper.sshExecuteCmd(sshConnection, "[ -f '/usr/sbin/dnsmasq' ]")) { + throw new ConfigurationException("Cannot find dnsmasq at /usr/sbin/dnsmasq on " + _ip); + } + + SCPClient scp = new SCPClient(sshConnection); + + String editHosts = "scripts/network/exdhcp/dnsmasq_edithosts.sh"; + String editHostsPath = Script.findScript("", editHosts); + if (editHostsPath == null) { + throw new ConfigurationException("Can not find script dnsmasq_edithosts.sh at " + editHosts); + } + scp.put(editHostsPath, "/usr/bin/", "0755"); + + String prepareDnsmasq = "scripts/network/exdhcp/prepare_dnsmasq.sh"; + String prepareDnsmasqPath = Script.findScript("", prepareDnsmasq); + if (prepareDnsmasqPath == null) { + throw new ConfigurationException("Can not find script prepare_dnsmasq.sh at " + prepareDnsmasq); + } + scp.put(prepareDnsmasqPath, "/usr/bin/", "0755"); + + /* + String prepareCmd = String.format("sh /usr/bin/prepare_dnsmasq.sh %1$s %2$s %3$s", _gateway, _dns, _ip); + if (!SSHCmdHelper.sshExecuteCmd(sshConnection, prepareCmd)) { + throw new ConfigurationException("prepare dnsmasq at " + _ip + " failed"); + } + */ + + s_logger.debug("Dnsmasq resource configure successfully"); + return true; + } catch (Exception e) { + s_logger.debug("Dnsmasq resorce configure failed", e); + throw new ConfigurationException(e.getMessage()); + } finally { + SSHCmdHelper.releaseSshConnection(sshConnection); + } + } + + @Override + public PingCommand getCurrentStatus(long id) { + com.trilead.ssh2.Connection sshConnection = SSHCmdHelper.acquireAuthorizedConnection(_ip, _username, _password); + if (sshConnection == null) { + return null; + } else { + SSHCmdHelper.releaseSshConnection(sshConnection); + return new PingRoutingCommand(getType(), id, new HashMap()); + } + } + + Answer execute(DhcpEntryCommand cmd) { + com.trilead.ssh2.Connection sshConnection = null; + try { + sshConnection = SSHCmdHelper.acquireAuthorizedConnection(_ip, _username, _password); + if (sshConnection == null) { + return new Answer(cmd, false, "ssh authenticate failed"); + } + String addDhcp = String.format("/usr/bin/dnsmasq_edithosts.sh %1$s %2$s %3$s", cmd.getVmMac(), cmd.getVmIpAddress(), cmd.getVmName()); + if (!SSHCmdHelper.sshExecuteCmd(sshConnection, addDhcp)) { + return new Answer(cmd, false, "add Dhcp entry failed"); + } else { + return new Answer(cmd); + } + } finally { + SSHCmdHelper.releaseSshConnection(sshConnection); + } + } + + @Override + public Answer executeRequest(Command cmd) { + if (cmd instanceof DhcpEntryCommand) { + return execute((DhcpEntryCommand)cmd); + } else { + return super.executeRequest(cmd); + } + } +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalKickStartPxeResource.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalKickStartPxeResource.java new file mode 100644 index 0000000000..cb4cfeeae5 --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalKickStartPxeResource.java @@ -0,0 +1,202 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +package com.cloud.baremetal.networkservice; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.naming.ConfigurationException; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.HostVmStateReportEntry; +import com.cloud.agent.api.PingCommand; +import com.cloud.agent.api.PingRoutingCommand; +import com.cloud.agent.api.routing.VmDataCommand; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.script.Script; +import com.cloud.utils.ssh.SSHCmdHelper; +import com.trilead.ssh2.SCPClient; + +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BaremetalKickStartPxeResource extends BaremetalPxeResourceBase { + private static final Logger s_logger = LoggerFactory.getLogger(BaremetalKickStartPxeResource.class); + private static final String Name = "BaremetalKickStartPxeResource"; + String _tftpDir; + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + super.configure(name, params); + _tftpDir = (String)params.get(BaremetalPxeService.PXE_PARAM_TFTP_DIR); + if (_tftpDir == null) { + throw new ConfigurationException("No tftp directory specified"); + } + + com.trilead.ssh2.Connection sshConnection = new com.trilead.ssh2.Connection(_ip, 22); + + s_logger.debug(String.format("Trying to connect to kickstart PXE server(IP=%1$s, username=%2$s, password=%3$s", _ip, _username, "******")); + try { + sshConnection.connect(null, 60000, 60000); + if (!sshConnection.authenticateWithPassword(_username, _password)) { + s_logger.debug("SSH Failed to authenticate"); + throw new ConfigurationException(String.format("Cannot connect to kickstart PXE server(IP=%1$s, username=%2$s, password=%3$s", _ip, _username, "******")); + } + + String cmd = String.format("[ -f /%1$s/pxelinux.0 ]", _tftpDir); + if (!SSHCmdHelper.sshExecuteCmd(sshConnection, cmd)) { + throw new ConfigurationException("Miss files in TFTP directory at " + _tftpDir + " check if pxelinux.0 are here"); + } + + SCPClient scp = new SCPClient(sshConnection); + String prepareScript = "scripts/network/ping/prepare_kickstart_bootfile.py"; + String prepareScriptPath = Script.findScript("", prepareScript); + if (prepareScriptPath == null) { + throw new ConfigurationException("Can not find prepare_kickstart_bootfile.py at " + prepareScript); + } + scp.put(prepareScriptPath, "/usr/bin/", "0755"); + + String cpScript = "scripts/network/ping/prepare_kickstart_kernel_initrd.py"; + String cpScriptPath = Script.findScript("", cpScript); + if (cpScriptPath == null) { + throw new ConfigurationException("Can not find prepare_kickstart_kernel_initrd.py at " + cpScript); + } + scp.put(cpScriptPath, "/usr/bin/", "0755"); + + String userDataScript = "scripts/network/ping/baremetal_user_data.py"; + String userDataScriptPath = Script.findScript("", userDataScript); + if (userDataScriptPath == null) { + throw new ConfigurationException("Can not find baremetal_user_data.py at " + userDataScript); + } + scp.put(userDataScriptPath, "/usr/bin/", "0755"); + + return true; + } catch (Exception e) { + throw new CloudRuntimeException(e); + } finally { + if (sshConnection != null) { + sshConnection.close(); + } + } + } + + @Override + public PingCommand getCurrentStatus(long id) { + com.trilead.ssh2.Connection sshConnection = SSHCmdHelper.acquireAuthorizedConnection(_ip, _username, _password); + if (sshConnection == null) { + return null; + } else { + SSHCmdHelper.releaseSshConnection(sshConnection); + return new PingRoutingCommand(getType(), id, new HashMap()); + } + } + + private Answer execute(VmDataCommand cmd) { + com.trilead.ssh2.Connection sshConnection = new com.trilead.ssh2.Connection(_ip, 22); + try { + List vmData = cmd.getVmData(); + StringBuilder sb = new StringBuilder(); + for (String[] data : vmData) { + String folder = data[0]; + String file = data[1]; + String contents = (data[2] == null) ? "none" : data[2]; + sb.append(cmd.getVmIpAddress()); + sb.append(","); + sb.append(folder); + sb.append(","); + sb.append(file); + sb.append(","); + sb.append(contents); + sb.append(";"); + } + String arg = StringUtils.stripEnd(sb.toString(), ";"); + + sshConnection.connect(null, 60000, 60000); + if (!sshConnection.authenticateWithPassword(_username, _password)) { + s_logger.debug("SSH Failed to authenticate"); + throw new ConfigurationException(String.format("Cannot connect to PING PXE server(IP=%1$s, username=%2$s, password=%3$s", _ip, _username, _password)); + } + + String script = String.format("python /usr/bin/baremetal_user_data.py '%s'", arg); + if (!SSHCmdHelper.sshExecuteCmd(sshConnection, script)) { + return new Answer(cmd, false, "Failed to add user data, command:" + script); + } + + return new Answer(cmd, true, "Success"); + } catch (Exception e) { + s_logger.debug("Prepare for creating baremetal template failed", e); + return new Answer(cmd, false, e.getMessage()); + } finally { + if (sshConnection != null) { + sshConnection.close(); + } + } + } + + @Override + public Answer executeRequest(Command cmd) { + if (cmd instanceof PrepareKickstartPxeServerCommand) { + return execute((PrepareKickstartPxeServerCommand)cmd); + } else if (cmd instanceof VmDataCommand) { + return execute((VmDataCommand)cmd); + } else { + return super.executeRequest(cmd); + } + } + + private Answer execute(PrepareKickstartPxeServerCommand cmd) { + com.trilead.ssh2.Connection sshConnection = new com.trilead.ssh2.Connection(_ip, 22); + try { + sshConnection.connect(null, 60000, 60000); + if (!sshConnection.authenticateWithPassword(_username, _password)) { + s_logger.debug("SSH Failed to authenticate"); + throw new ConfigurationException(String.format("Cannot connect to PING PXE server(IP=%1$s, username=%2$s, password=%3$s", _ip, _username, _password)); + } + + String copyTo = String.format("%s/%s", _tftpDir, cmd.getTemplateUuid()); + String script = String.format("python /usr/bin/prepare_kickstart_kernel_initrd.py %s %s %s", cmd.getKernel(), cmd.getInitrd(), copyTo); + + if (!SSHCmdHelper.sshExecuteCmd(sshConnection, script)) { + return new Answer(cmd, false, "prepare kickstart at pxe server " + _ip + " failed, command:" + script); + } + + String kernelPath = String.format("%s/vmlinuz", cmd.getTemplateUuid()); + String initrdPath = String.format("%s/initrd.img", cmd.getTemplateUuid()); + script = + String.format("python /usr/bin/prepare_kickstart_bootfile.py %s %s %s %s %s %s", _tftpDir, cmd.getMac(), kernelPath, initrdPath, cmd.getKsFile(), + cmd.getMac()); + if (!SSHCmdHelper.sshExecuteCmd(sshConnection, script)) { + return new Answer(cmd, false, "prepare kickstart at pxe server " + _ip + " failed, command:" + script); + } + + s_logger.debug("Prepare kickstart PXE server successfully"); + return new Answer(cmd, true, "Success"); + } catch (Exception e) { + s_logger.debug("Prepare for kickstart server failed", e); + return new Answer(cmd, false, e.getMessage()); + } finally { + if (sshConnection != null) { + sshConnection.close(); + } + } + } +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalKickStartServiceImpl.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalKickStartServiceImpl.java new file mode 100644 index 0000000000..58992f0fc3 --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalKickStartServiceImpl.java @@ -0,0 +1,399 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +package com.cloud.baremetal.networkservice; + +import java.io.File; +import java.net.URI; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.baremetal.IpmISetBootDevCommand; +import com.cloud.agent.api.baremetal.IpmISetBootDevCommand.BootDev; +import com.cloud.baremetal.database.BaremetalPxeDao; +import com.cloud.baremetal.database.BaremetalPxeVO; +import com.cloud.baremetal.networkservice.BaremetalPxeManager.BaremetalPxeType; +import com.cloud.configuration.Config; +import com.cloud.dc.DataCenter; +import com.cloud.deploy.DeployDestination; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDetailsDao; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.network.Network; +import com.cloud.network.PhysicalNetworkServiceProvider; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.dao.PhysicalNetworkDao; +import com.cloud.network.dao.PhysicalNetworkServiceProviderDao; +import com.cloud.network.dao.PhysicalNetworkServiceProviderVO; +import com.cloud.network.dao.PhysicalNetworkVO; +import com.cloud.network.guru.ControlNetworkGuru; +import com.cloud.network.router.VirtualRouter; +import com.cloud.resource.ResourceManager; +import com.cloud.resource.ServerResource; +import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.uservm.UserVm; +import com.cloud.utils.Pair; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.QueryBuilder; +import com.cloud.utils.db.SearchCriteria.Op; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.ssh.SshHelper; +import com.cloud.vm.DomainRouterVO; +import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; +import com.cloud.vm.ReservationContext; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.DomainRouterDao; +import com.cloud.vm.dao.NicDao; + +import org.apache.cloudstack.api.AddBaremetalKickStartPxeCmd; +import org.apache.cloudstack.api.AddBaremetalPxeCmd; +import org.apache.cloudstack.api.ListBaremetalPxeServersCmd; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BaremetalKickStartServiceImpl extends BareMetalPxeServiceBase implements BaremetalPxeService { + private static final Logger s_logger = LoggerFactory.getLogger(BaremetalKickStartServiceImpl.class); + @Inject + ResourceManager _resourceMgr; + @Inject + PhysicalNetworkDao _physicalNetworkDao; + @Inject + PhysicalNetworkServiceProviderDao _physicalNetworkServiceProviderDao; + @Inject + HostDetailsDao _hostDetailsDao; + @Inject + BaremetalPxeDao _pxeDao; + @Inject + NetworkDao _nwDao; + @Inject + VMTemplateDao _tmpDao; + @Inject + DomainRouterDao _routerDao; + @Inject + NicDao _nicDao; + @Inject + ConfigurationDao _configDao; + + private DomainRouterVO getVirtualRouter(Network network) { + final List routers = _routerDao.listByNetworkAndRole(network.getId(), VirtualRouter.Role.VIRTUAL_ROUTER); + + if (routers.isEmpty()) { + throw new CloudRuntimeException(String.format("cannot find any running virtual router on network[id:%s, uuid:%s]", network.getId(), network.getUuid())); + } + + if (routers.size() > 1) { + throw new CloudRuntimeException(String.format("baremetal hasn't supported redundant router yet")); + } + + final DomainRouterVO vr = routers.get(0); + if (Hypervisor.HypervisorType.KVM.equals(vr.getHypervisorType()) || Hypervisor.HypervisorType.XenServer.equals(vr.getHypervisorType())) { + throw new CloudRuntimeException(String.format("baremetal only support vmware virtual router, but get %s", vr.getHypervisorType())); + } + + return vr; + } + + private List parseKickstartUrl(VirtualMachineProfile profile) { + final String tpl = profile.getTemplate().getUrl(); + assert tpl != null : "How can a null template get here!!!"; + final String[] tpls = tpl.split(";"); + final CloudRuntimeException err = + new CloudRuntimeException( + String.format( + "template url[%s] is not correctly encoded. it must be in format of ks=http_link_to_kickstartfile;kernel=nfs_path_to_pxe_kernel;initrd=nfs_path_to_pxe_initrd", + tpl)); + if (tpls.length != 3) { + throw err; + } + + String ks = null; + String kernel = null; + String initrd = null; + + for (final String t : tpls) { + final String[] kv = t.split("="); + if (kv.length != 2) { + throw err; + } + if (kv[0].equals("ks")) { + ks = kv[1]; + } else if (kv[0].equals("kernel")) { + kernel = kv[1]; + } else if (kv[0].equals("initrd")) { + initrd = kv[1]; + } else { + throw err; + } + } + + return Arrays.asList(ks, kernel, initrd); + } + + public File getSystemVMKeyFile() { + final URL url = this.getClass().getClassLoader().getResource("scripts/vm/systemvm/id_rsa.cloud"); + File keyFile = null; + if (url != null) { + keyFile = new File(url.getPath()); + } + if (keyFile == null || !keyFile.exists()) { + keyFile = new File("/usr/share/cloudstack-common/scripts/vm/systemvm/id_rsa.cloud"); + } + if (!keyFile.exists()) { + throw new CloudRuntimeException(String.format("cannot find id_rsa.cloud")); + } + if (!keyFile.exists()) { + s_logger.error("Unable to locate id_rsa.cloud in your setup at " + keyFile.toString()); + } + return keyFile; + } + + private boolean preparePxeInBasicZone(VirtualMachineProfile profile, NicProfile nic, DeployDestination dest, ReservationContext context) throws AgentUnavailableException, OperationTimedoutException { + final NetworkVO nwVO = _nwDao.findById(nic.getNetworkId()); + final QueryBuilder sc = QueryBuilder.create(BaremetalPxeVO.class); + sc.and(sc.entity().getDeviceType(), Op.EQ, BaremetalPxeType.KICK_START.toString()); + sc.and(sc.entity().getPhysicalNetworkId(), Op.EQ, nwVO.getPhysicalNetworkId()); + final BaremetalPxeVO pxeVo = sc.find(); + if (pxeVo == null) { + throw new CloudRuntimeException("No kickstart PXE server found in pod: " + dest.getPod().getId() + ", you need to add it before starting VM"); + } + final VMTemplateVO template = _tmpDao.findById(profile.getTemplateId()); + final List tuple = parseKickstartUrl(profile); + + final String ks = tuple.get(0); + final String kernel = tuple.get(1); + final String initrd = tuple.get(2); + + final PrepareKickstartPxeServerCommand cmd = new PrepareKickstartPxeServerCommand(); + cmd.setKsFile(ks); + cmd.setInitrd(initrd); + cmd.setKernel(kernel); + cmd.setMac(nic.getMacAddress()); + cmd.setTemplateUuid(template.getUuid()); + final Answer aws = _agentMgr.send(pxeVo.getHostId(), cmd); + if (!aws.getResult()) { + s_logger.warn("Unable to set host: " + dest.getHost().getId() + " to PXE boot because " + aws.getDetails()); + return false; + } + + return true; + } + + private boolean preparePxeInAdvancedZone(VirtualMachineProfile profile, NicProfile nic, Network network, DeployDestination dest, ReservationContext context) throws Exception { + final DomainRouterVO vr = getVirtualRouter(network); + final List nics = _nicDao.listByVmId(vr.getId()); + NicVO mgmtNic = null; + for (final NicVO nicvo : nics) { + if (ControlNetworkGuru.class.getSimpleName().equals(nicvo.getReserver())) { + mgmtNic = nicvo; + break; + } + } + + if (mgmtNic == null) { + throw new CloudRuntimeException(String.format("cannot find management nic on virtual router[id:%s]", vr.getId())); + } + + final String internalServerIp = _configDao.getValue(Config.BaremetalInternalStorageServer.key()); + if (internalServerIp == null) { + throw new CloudRuntimeException(String.format("please specify 'baremetal.internal.storage.server.ip', which is the http server/nfs server storing kickstart files and ISO files, in global setting")); + } + + final List tuple = parseKickstartUrl(profile); + String cmd = String.format("/opt/cloud/bin/prepare_pxe.sh %s %s %s %s %s %s", tuple.get(1), tuple.get(2), profile.getTemplate().getUuid(), + String.format("01-%s", nic.getMacAddress().replaceAll(":", "-")).toLowerCase(), tuple.get(0), nic.getMacAddress().toLowerCase()); + s_logger.debug(String.format("prepare pxe on virtual router[ip:%s], cmd: %s", mgmtNic.getIPv4Address(), cmd)); + Pair ret = SshHelper.sshExecute(mgmtNic.getIPv4Address(), 3922, "root", getSystemVMKeyFile(), null, cmd); + if (!ret.first()) { + throw new CloudRuntimeException(String.format("failed preparing PXE in virtual router[id:%s], because %s", vr.getId(), ret.second())); + } + + //String internalServerIp = "10.223.110.231"; + cmd = String.format("/opt/cloud/bin/baremetal_snat.sh %s %s %s", mgmtNic.getIPv4Address(), internalServerIp, mgmtNic.getIPv4Gateway()); + s_logger.debug(String.format("prepare SNAT on virtual router[ip:%s], cmd: %s", mgmtNic.getIPv4Address(), cmd)); + ret = SshHelper.sshExecute(mgmtNic.getIPv4Address(), 3922, "root", getSystemVMKeyFile(), null, cmd); + if (!ret.first()) { + throw new CloudRuntimeException(String.format("failed preparing PXE in virtual router[id:%s], because %s", vr.getId(), ret.second())); + } + + return true; + } + + @Override + public boolean prepare(VirtualMachineProfile profile, NicProfile nic, Network network, DeployDestination dest, ReservationContext context) { + try { + if (DataCenter.NetworkType.Basic.equals(dest.getDataCenter().getNetworkType())) { + if (!preparePxeInBasicZone(profile, nic, dest, context)) { + return false; + } + } else { + if (!preparePxeInAdvancedZone(profile, nic, network, dest, context)) { + return false; + } + } + + final IpmISetBootDevCommand bootCmd = new IpmISetBootDevCommand(BootDev.pxe); + final Answer aws = _agentMgr.send(dest.getHost().getId(), bootCmd); + if (!aws.getResult()) { + s_logger.warn("Unable to set host: " + dest.getHost().getId() + " to PXE boot because " + aws.getDetails()); + } + + return aws.getResult(); + } catch (final Exception e) { + s_logger.warn("Cannot prepare PXE server", e); + return false; + } + } + + @Override + public boolean prepareCreateTemplate(Long pxeServerId, UserVm vm, String templateUrl) { + // TODO Auto-generated method stub + return false; + } + + @Override + @DB + public BaremetalPxeVO addPxeServer(AddBaremetalPxeCmd cmd) { + final AddBaremetalKickStartPxeCmd kcmd = (AddBaremetalKickStartPxeCmd)cmd; + PhysicalNetworkVO pNetwork = null; + long zoneId; + + if (cmd.getPhysicalNetworkId() == null || cmd.getUrl() == null || cmd.getUsername() == null || cmd.getPassword() == null) { + throw new IllegalArgumentException("At least one of the required parameters(physical network id, url, username, password) is null"); + } + + pNetwork = _physicalNetworkDao.findById(cmd.getPhysicalNetworkId()); + if (pNetwork == null) { + throw new IllegalArgumentException("Could not find phyical network with ID: " + cmd.getPhysicalNetworkId()); + } + zoneId = pNetwork.getDataCenterId(); + + final PhysicalNetworkServiceProviderVO ntwkSvcProvider = + _physicalNetworkServiceProviderDao.findByServiceProvider(pNetwork.getId(), BaremetalPxeManager.BAREMETAL_PXE_SERVICE_PROVIDER.getName()); + if (ntwkSvcProvider == null) { + throw new CloudRuntimeException("Network Service Provider: " + BaremetalPxeManager.BAREMETAL_PXE_SERVICE_PROVIDER.getName() + + " is not enabled in the physical network: " + cmd.getPhysicalNetworkId() + "to add this device"); + } else if (ntwkSvcProvider.getState() == PhysicalNetworkServiceProvider.State.Shutdown) { + throw new CloudRuntimeException("Network Service Provider: " + ntwkSvcProvider.getProviderName() + " is in shutdown state in the physical network: " + + cmd.getPhysicalNetworkId() + "to add this device"); + } + + final List pxes = _resourceMgr.listAllHostsInOneZoneByType(Host.Type.BaremetalPxe, zoneId); + if (!pxes.isEmpty()) { + throw new IllegalArgumentException("Already had a PXE server zone: " + zoneId); + } + + final String tftpDir = kcmd.getTftpDir(); + if (tftpDir == null) { + throw new IllegalArgumentException("No TFTP directory specified"); + } + + URI uri; + try { + uri = new URI(cmd.getUrl()); + } catch (final Exception e) { + s_logger.debug(e.toString()); + throw new IllegalArgumentException(e.getMessage()); + } + String ipAddress = uri.getHost(); + if (ipAddress == null) { + ipAddress = cmd.getUrl(); + } + + final String guid = getPxeServerGuid(Long.toString(zoneId), BaremetalPxeType.KICK_START.toString(), ipAddress); + + ServerResource resource = null; + final Map params = new HashMap(); + params.put(BaremetalPxeService.PXE_PARAM_ZONE, Long.toString(zoneId)); + params.put(BaremetalPxeService.PXE_PARAM_IP, ipAddress); + params.put(BaremetalPxeService.PXE_PARAM_USERNAME, cmd.getUsername()); + params.put(BaremetalPxeService.PXE_PARAM_PASSWORD, cmd.getPassword()); + params.put(BaremetalPxeService.PXE_PARAM_TFTP_DIR, tftpDir); + params.put(BaremetalPxeService.PXE_PARAM_GUID, guid); + resource = new BaremetalKickStartPxeResource(); + try { + resource.configure("KickStart PXE resource", params); + } catch (final Exception e) { + throw new CloudRuntimeException(e.getMessage(), e); + } + + final Host pxeServer = _resourceMgr.addHost(zoneId, resource, Host.Type.BaremetalPxe, params); + if (pxeServer == null) { + throw new CloudRuntimeException("Cannot add PXE server as a host"); + } + + final BaremetalPxeVO vo = new BaremetalPxeVO(); + vo.setHostId(pxeServer.getId()); + vo.setNetworkServiceProviderId(ntwkSvcProvider.getId()); + vo.setPhysicalNetworkId(kcmd.getPhysicalNetworkId()); + vo.setDeviceType(BaremetalPxeType.KICK_START.toString()); + _pxeDao.persist(vo); + return vo; + } + + @Override + public BaremetalPxeResponse getApiResponse(BaremetalPxeVO vo) { + final BaremetalPxeResponse response = new BaremetalPxeResponse(); + response.setId(vo.getUuid()); + final HostVO host = _hostDao.findById(vo.getHostId()); + response.setUrl(host.getPrivateIpAddress()); + final PhysicalNetworkServiceProviderVO providerVO = _physicalNetworkServiceProviderDao.findById(vo.getNetworkServiceProviderId()); + response.setPhysicalNetworkId(providerVO.getUuid()); + final PhysicalNetworkVO nwVO = _physicalNetworkDao.findById(vo.getPhysicalNetworkId()); + response.setPhysicalNetworkId(nwVO.getUuid()); + response.setObjectName("baremetalpxeserver"); + return response; + } + + @Override + public List listPxeServers(ListBaremetalPxeServersCmd cmd) { + final List responses = new ArrayList(); + if (cmd.getId() != null) { + final BaremetalPxeVO vo = _pxeDao.findById(cmd.getId()); + responses.add(getApiResponse(vo)); + return responses; + } + + final QueryBuilder sc = QueryBuilder.create(BaremetalPxeVO.class); + sc.and(sc.entity().getPhysicalNetworkId(), Op.EQ, cmd.getPhysicalNetworkId()); + final List vos = sc.list(); + for (final BaremetalPxeVO vo : vos) { + responses.add(getApiResponse(vo)); + } + return responses; + } + + @Override + public String getPxeServiceType() { + return BaremetalPxeManager.BaremetalPxeType.KICK_START.toString(); + } + +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPingPxeResource.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPingPxeResource.java new file mode 100644 index 0000000000..dba1b628a5 --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPingPxeResource.java @@ -0,0 +1,259 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +// Apache License, Version 2.0 (the "License"); you may not use this +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// Automatically generated by addcopyright.py at 04/03/2012 +package com.cloud.baremetal.networkservice; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.naming.ConfigurationException; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.HostVmStateReportEntry; +import com.cloud.agent.api.PingCommand; +import com.cloud.agent.api.PingRoutingCommand; +import com.cloud.agent.api.baremetal.PrepareCreateTemplateCommand; +import com.cloud.agent.api.baremetal.PreparePxeServerAnswer; +import com.cloud.agent.api.baremetal.PreparePxeServerCommand; +import com.cloud.agent.api.routing.VmDataCommand; +import com.cloud.utils.script.Script; +import com.cloud.utils.ssh.SSHCmdHelper; +import com.trilead.ssh2.SCPClient; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BaremetalPingPxeResource extends BaremetalPxeResourceBase { + private static final Logger s_logger = LoggerFactory.getLogger(BaremetalPingPxeResource.class); + private static final String Name = "BaremetalPingPxeResource"; + String _storageServer; + String _pingDir; + String _share; + String _dir; + String _tftpDir; + String _cifsUserName; + String _cifsPassword; + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + super.configure(name, params); + _storageServer = (String)params.get(BaremetalPxeService.PXE_PARAM_PING_STORAGE_SERVER_IP); + _pingDir = (String)params.get(BaremetalPxeService.PXE_PARAM_PING_ROOT_DIR); + _tftpDir = (String)params.get(BaremetalPxeService.PXE_PARAM_TFTP_DIR); + _cifsUserName = (String)params.get(BaremetalPxeService.PXE_PARAM_PING_STORAGE_SERVER_USERNAME); + _cifsPassword = (String)params.get(BaremetalPxeService.PXE_PARAM_PING_STORAGE_SERVER_PASSWORD); + + if (_podId == null) { + throw new ConfigurationException("No Pod specified"); + } + + if (_storageServer == null) { + throw new ConfigurationException("No stroage server specified"); + } + + if (_tftpDir == null) { + throw new ConfigurationException("No tftp directory specified"); + } + + if (_pingDir == null) { + throw new ConfigurationException("No PING directory specified"); + } + + if (_cifsUserName == null || _cifsUserName.equalsIgnoreCase("")) { + _cifsUserName = "xxx"; + } + + if (_cifsPassword == null || _cifsPassword.equalsIgnoreCase("")) { + _cifsPassword = "xxx"; + } + + String pingDirs[] = _pingDir.split("/"); + if (pingDirs.length != 2) { + throw new ConfigurationException("PING dir should have format like myshare/direcotry, eg: windows/64bit"); + } + _share = pingDirs[0]; + _dir = pingDirs[1]; + + com.trilead.ssh2.Connection sshConnection = new com.trilead.ssh2.Connection(_ip, 22); + + s_logger.debug(String.format("Trying to connect to PING PXE server(IP=%1$s, username=%2$s, password=%3$s", _ip, _username, "******")); + try { + sshConnection.connect(null, 60000, 60000); + if (!sshConnection.authenticateWithPassword(_username, _password)) { + s_logger.debug("SSH Failed to authenticate"); + throw new ConfigurationException(String.format("Cannot connect to PING PXE server(IP=%1$s, username=%2$s, password=%3$s", _ip, _username, "******")); + } + + String cmd = String.format("[ -f /%1$s/pxelinux.0 ] && [ -f /%2$s/kernel ] && [ -f /%3$s/initrd.gz ] ", _tftpDir, _tftpDir, _tftpDir); + if (!SSHCmdHelper.sshExecuteCmd(sshConnection, cmd)) { + throw new ConfigurationException("Miss files in TFTP directory at " + _tftpDir + " check if pxelinux.0, kernel initrd.gz are here"); + } + + SCPClient scp = new SCPClient(sshConnection); + String prepareScript = "scripts/network/ping/prepare_tftp_bootfile.py"; + String prepareScriptPath = Script.findScript("", prepareScript); + if (prepareScriptPath == null) { + throw new ConfigurationException("Can not find prepare_tftp_bootfile.py at " + prepareScriptPath); + } + scp.put(prepareScriptPath, "/usr/bin/", "0755"); + + String userDataScript = "scripts/network/ping/baremetal_user_data.py"; + String userDataScriptPath = Script.findScript("", userDataScript); + if (userDataScriptPath == null) { + throw new ConfigurationException("Can not find baremetal_user_data.py at " + userDataScriptPath); + } + scp.put(userDataScriptPath, "/usr/bin/", "0755"); + + return true; + } catch (Exception e) { + throw new ConfigurationException(e.getMessage()); + } finally { + if (sshConnection != null) { + sshConnection.close(); + } + } + } + + @Override + public PingCommand getCurrentStatus(long id) { + com.trilead.ssh2.Connection sshConnection = SSHCmdHelper.acquireAuthorizedConnection(_ip, _username, _password); + if (sshConnection == null) { + return null; + } else { + SSHCmdHelper.releaseSshConnection(sshConnection); + return new PingRoutingCommand(getType(), id, new HashMap()); + } + } + + protected PreparePxeServerAnswer execute(PreparePxeServerCommand cmd) { + com.trilead.ssh2.Connection sshConnection = new com.trilead.ssh2.Connection(_ip, 22); + try { + sshConnection.connect(null, 60000, 60000); + if (!sshConnection.authenticateWithPassword(_username, _password)) { + s_logger.debug("SSH Failed to authenticate"); + throw new ConfigurationException(String.format("Cannot connect to PING PXE server(IP=%1$s, username=%2$s, password=%3$s", _ip, _username, _password)); + } + + String script = + String.format("python /usr/bin/prepare_tftp_bootfile.py restore %1$s %2$s %3$s %4$s %5$s %6$s %7$s %8$s %9$s %10$s %11$s", _tftpDir, cmd.getMac(), + _storageServer, _share, _dir, cmd.getTemplate(), _cifsUserName, _cifsPassword, cmd.getIp(), cmd.getNetMask(), cmd.getGateWay()); + if (!SSHCmdHelper.sshExecuteCmd(sshConnection, script)) { + return new PreparePxeServerAnswer(cmd, "prepare PING at " + _ip + " failed, command:" + script); + } + s_logger.debug("Prepare Ping PXE server successfully"); + + return new PreparePxeServerAnswer(cmd); + } catch (Exception e) { + s_logger.debug("Prepare PING pxe server failed", e); + return new PreparePxeServerAnswer(cmd, e.getMessage()); + } finally { + if (sshConnection != null) { + sshConnection.close(); + } + } + } + + protected Answer execute(PrepareCreateTemplateCommand cmd) { + com.trilead.ssh2.Connection sshConnection = new com.trilead.ssh2.Connection(_ip, 22); + try { + sshConnection.connect(null, 60000, 60000); + if (!sshConnection.authenticateWithPassword(_username, _password)) { + s_logger.debug("SSH Failed to authenticate"); + throw new ConfigurationException(String.format("Cannot connect to PING PXE server(IP=%1$s, username=%2$s, password=%3$s", _ip, _username, _password)); + } + + String script = + String.format("python /usr/bin/prepare_tftp_bootfile.py backup %1$s %2$s %3$s %4$s %5$s %6$s %7$s %8$s %9$s %10$s %11$s", _tftpDir, cmd.getMac(), + _storageServer, _share, _dir, cmd.getTemplate(), _cifsUserName, _cifsPassword, cmd.getIp(), cmd.getNetMask(), cmd.getGateWay()); + if (!SSHCmdHelper.sshExecuteCmd(sshConnection, script)) { + return new Answer(cmd, false, "prepare for creating template failed, command:" + script); + } + s_logger.debug("Prepare for creating template successfully"); + + return new Answer(cmd, true, "Success"); + } catch (Exception e) { + s_logger.debug("Prepare for creating baremetal template failed", e); + return new Answer(cmd, false, e.getMessage()); + } finally { + if (sshConnection != null) { + sshConnection.close(); + } + } + } + + @Override + public Answer executeRequest(Command cmd) { + if (cmd instanceof PreparePxeServerCommand) { + return execute((PreparePxeServerCommand)cmd); + } else if (cmd instanceof PrepareCreateTemplateCommand) { + return execute((PrepareCreateTemplateCommand)cmd); + } else if (cmd instanceof VmDataCommand) { + return execute((VmDataCommand)cmd); + } else { + return super.executeRequest(cmd); + } + } + + private Answer execute(VmDataCommand cmd) { + com.trilead.ssh2.Connection sshConnection = new com.trilead.ssh2.Connection(_ip, 22); + try { + List vmData = cmd.getVmData(); + StringBuilder sb = new StringBuilder(); + for (String[] data : vmData) { + String folder = data[0]; + String file = data[1]; + String contents = (data[2] == null) ? "none" : data[2]; + sb.append(cmd.getVmIpAddress()); + sb.append(","); + sb.append(folder); + sb.append(","); + sb.append(file); + sb.append(","); + sb.append(contents); + sb.append(";"); + } + String arg = org.apache.commons.lang.StringUtils.stripEnd(sb.toString(), ";"); + + sshConnection.connect(null, 60000, 60000); + if (!sshConnection.authenticateWithPassword(_username, _password)) { + s_logger.debug("SSH Failed to authenticate"); + throw new ConfigurationException(String.format("Cannot connect to PING PXE server(IP=%1$s, username=%2$s, password=%3$s", _ip, _username, _password)); + } + + String script = String.format("python /usr/bin/baremetal_user_data.py '%s'", arg); + if (!SSHCmdHelper.sshExecuteCmd(sshConnection, script)) { + return new Answer(cmd, false, "Failed to add user data, command:" + script); + } + + return new Answer(cmd, true, "Success"); + } catch (Exception e) { + s_logger.debug("Prepare for creating baremetal template failed", e); + return new Answer(cmd, false, e.getMessage()); + } finally { + if (sshConnection != null) { + sshConnection.close(); + } + } + } +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeElement.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeElement.java new file mode 100644 index 0000000000..d377828701 --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeElement.java @@ -0,0 +1,204 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +package com.cloud.baremetal.networkservice; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import javax.inject.Inject; + +import com.cloud.baremetal.database.BaremetalPxeVO; +import com.cloud.baremetal.manager.BaremetalVlanManager; +import com.cloud.dc.DataCenter; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.Pod; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.deploy.DeployDestination; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.IllegalVirtualMachineException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.network.Network; +import com.cloud.network.Network.Capability; +import com.cloud.network.Network.GuestType; +import com.cloud.network.Network.Provider; +import com.cloud.network.Network.Service; +import com.cloud.network.Networks.TrafficType; +import com.cloud.network.PhysicalNetworkServiceProvider; +import com.cloud.network.element.NetworkElement; +import com.cloud.offering.NetworkOffering; +import com.cloud.utils.component.AdapterBase; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.QueryBuilder; +import com.cloud.utils.db.SearchCriteria.Op; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; +import com.cloud.vm.ReservationContext; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine.Type; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.VMInstanceDao; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BaremetalPxeElement extends AdapterBase implements NetworkElement { + private static final Logger s_logger = LoggerFactory.getLogger(BaremetalPxeElement.class); + private static final Map> capabilities; + + @Inject + BaremetalPxeManager _pxeMgr;; + @Inject + VMInstanceDao _vmDao; + @Inject + NicDao _nicDao; + @Inject + BaremetalVlanManager vlanMgr; + @Inject + DataCenterDao zoneDao; + + static { + final Capability cap = new Capability(BaremetalPxeManager.BAREMETAL_PXE_CAPABILITY); + final Map baremetalCaps = new HashMap(); + baremetalCaps.put(cap, null); + capabilities = new HashMap>(); + capabilities.put(BaremetalPxeManager.BAREMETAL_PXE_SERVICE, baremetalCaps); + } + + @Override + public Map> getCapabilities() { + return capabilities; + } + + @Override + public Provider getProvider() { + return BaremetalPxeManager.BAREMETAL_PXE_SERVICE_PROVIDER; + } + + private boolean canHandle(DeployDestination dest, TrafficType trafficType, GuestType networkType) { + final Pod pod = dest.getPod(); + if (pod != null && trafficType == TrafficType.Guest) { + final QueryBuilder sc = QueryBuilder.create(BaremetalPxeVO.class); + sc.and(sc.entity().getPodId(), Op.EQ, pod.getId()); + return sc.find() != null; + } + + return false; + } + + @Override + public boolean implement(Network network, NetworkOffering offering, DeployDestination dest, ReservationContext context) throws ConcurrentOperationException, + ResourceUnavailableException, InsufficientCapacityException { + if (dest.getDataCenter().getNetworkType() == DataCenter.NetworkType.Advanced){ + return true; + } + + if (offering.isSystemOnly() || !canHandle(dest, offering.getTrafficType(), network.getGuestType())) { + s_logger.debug("BaremetalPxeElement can not handle network offering: " + offering.getName()); + return false; + } + return true; + } + + @Override + @DB + public boolean prepare(Network network, NicProfile nic, VirtualMachineProfile vm, DeployDestination dest, ReservationContext context) + throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException, IllegalVirtualMachineException { + if (vm.getType() != Type.User || vm.getHypervisorType() != HypervisorType.BareMetal) { + throw new IllegalVirtualMachineException("Illegal VM type informed. Excpeted USER VM, but got -> " + vm.getType()); + } + + final VMInstanceVO vo = _vmDao.findById(vm.getId()); + assert vo != null : "Where ths nic " + nic.getId() + " going???"; + if (vo.getLastHostId() == null) { + nic.setMacAddress(dest.getHost().getPrivateMacAddress()); + final NicVO nicVo = _nicDao.findById(nic.getId()); + nicVo.setMacAddress(nic.getMacAddress()); + _nicDao.update(nicVo.getId(), nicVo); + + /*This vm is just being created */ + if (!_pxeMgr.prepare(vm, nic, network, dest, context)) { + throw new CloudRuntimeException("Cannot prepare pxe server"); + } + } + + if (dest.getDataCenter().getNetworkType() == DataCenter.NetworkType.Advanced){ + prepareVlan(network, dest); + } + + return true; + } + + private void prepareVlan(Network network, DeployDestination dest) { + vlanMgr.prepareVlan(network, dest); + } + + @Override + public boolean release(Network network, NicProfile nic, VirtualMachineProfile vm, ReservationContext context) throws ConcurrentOperationException, + ResourceUnavailableException { + if (vm.getType() != Type.User || vm.getHypervisorType() != HypervisorType.BareMetal) { + return false; + } + + final DataCenterVO dc = zoneDao.findById(vm.getVirtualMachine().getDataCenterId()); + if (dc.getNetworkType() == DataCenter.NetworkType.Advanced) { + releaseVlan(network, vm); + } + return true; + } + + private void releaseVlan(Network network, VirtualMachineProfile vm) { + vlanMgr.releaseVlan(network, vm); + } + + @Override + public boolean shutdown(Network network, ReservationContext context, boolean cleanup) throws ConcurrentOperationException, ResourceUnavailableException { + return true; + } + + @Override + public boolean isReady(PhysicalNetworkServiceProvider provider) { + return true; + } + + @Override + public boolean shutdownProviderInstances(PhysicalNetworkServiceProvider provider, ReservationContext context) throws ConcurrentOperationException, + ResourceUnavailableException { + return true; + } + + @Override + public boolean canEnableIndividualServices() { + return false; + } + + @Override + public boolean destroy(Network network, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException { + return true; + } + + @Override + public boolean verifyServicesCombination(Set services) { + return true; + } +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeKickStartResponse.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeKickStartResponse.java new file mode 100644 index 0000000000..b5932847d0 --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeKickStartResponse.java @@ -0,0 +1,41 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +package com.cloud.baremetal.networkservice; + +import com.cloud.baremetal.database.BaremetalPxeVO; +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.EntityReference; + +@EntityReference(value = BaremetalPxeVO.class) +public class BaremetalPxeKickStartResponse extends BaremetalPxeResponse { + @SerializedName(ApiConstants.TFTP_DIR) + @Param(description = "Tftp root directory of PXE server") + private String tftpDir; + + public String getTftpDir() { + return tftpDir; + } + + public void setTftpDir(String tftpDir) { + this.tftpDir = tftpDir; + } +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeManager.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeManager.java new file mode 100644 index 0000000000..d6991bf324 --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeManager.java @@ -0,0 +1,66 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +// Apache License, Version 2.0 (the "License"); you may not use this +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// Automatically generated by addcopyright.py at 04/03/2012 +package com.cloud.baremetal.networkservice; + +import java.util.List; + +import com.cloud.baremetal.database.BaremetalPxeVO; +import com.cloud.deploy.DeployDestination; +import com.cloud.host.HostVO; +import com.cloud.network.Network; +import com.cloud.network.Network.Provider; +import com.cloud.uservm.UserVm; +import com.cloud.utils.component.Manager; +import com.cloud.utils.component.PluggableService; +import com.cloud.vm.NicProfile; +import com.cloud.vm.ReservationContext; +import com.cloud.vm.VirtualMachineProfile; + +import org.apache.cloudstack.api.AddBaremetalPxeCmd; +import org.apache.cloudstack.api.ListBaremetalPxeServersCmd; + +public interface BaremetalPxeManager extends Manager, PluggableService { + public enum BaremetalPxeType { + PING, KICK_START, + } + + boolean prepare(VirtualMachineProfile profile, NicProfile nic, Network network, DeployDestination dest, ReservationContext context); + + boolean prepareCreateTemplate(Long pxeServerId, UserVm vm, String templateUrl); + + BaremetalPxeType getPxeServerType(HostVO host); + + BaremetalPxeVO addPxeServer(AddBaremetalPxeCmd cmd); + + BaremetalPxeResponse getApiResponse(BaremetalPxeVO vo); + + List listPxeServers(ListBaremetalPxeServersCmd cmd); + + boolean addUserData(NicProfile nic, VirtualMachineProfile vm); + + public static final Network.Service BAREMETAL_PXE_SERVICE = new Network.Service("BaremetalPxeService"); + public static final String BAREMETAL_PXE_CAPABILITY = "BaremetalPxe"; + public static final String BAREMETAL_PXE_SERVICE_PROPERTIES = "baremetalpxe_commands.properties"; + public static final Provider BAREMETAL_PXE_SERVICE_PROVIDER = new Provider("BaremetalPxeProvider", true);; + public static final Provider BAREMETAL_USERDATA_PROVIDER = new Provider("BaremetalUserdataProvider", true); +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeManagerImpl.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeManagerImpl.java new file mode 100644 index 0000000000..073e040309 --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeManagerImpl.java @@ -0,0 +1,256 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +// Apache License, Version 2.0 (the "License"); you may not use this +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// Automatically generated by addcopyright.py at 04/03/2012 +package com.cloud.baremetal.networkservice; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.StartupCommand; +import com.cloud.agent.api.StartupPxeServerCommand; +import com.cloud.agent.api.routing.VmDataCommand; +import com.cloud.baremetal.database.BaremetalPxeVO; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.deploy.DeployDestination; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.network.Network; +import com.cloud.network.NetworkModel; +import com.cloud.network.dao.PhysicalNetworkDao; +import com.cloud.network.dao.PhysicalNetworkVO; +import com.cloud.resource.ResourceManager; +import com.cloud.resource.ResourceStateAdapter; +import com.cloud.resource.ServerResource; +import com.cloud.resource.UnableDeleteHostException; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.uservm.UserVm; +import com.cloud.utils.StringUtils; +import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.db.QueryBuilder; +import com.cloud.utils.db.SearchCriteria.Op; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; +import com.cloud.vm.ReservationContext; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.UserVmDao; + +import org.apache.cloudstack.api.AddBaremetalKickStartPxeCmd; +import org.apache.cloudstack.api.AddBaremetalPxeCmd; +import org.apache.cloudstack.api.AddBaremetalPxePingServerCmd; +import org.apache.cloudstack.api.ListBaremetalPxeServersCmd; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BaremetalPxeManagerImpl extends ManagerBase implements BaremetalPxeManager, ResourceStateAdapter { + private static final Logger s_logger = LoggerFactory.getLogger(BaremetalPxeManagerImpl.class); + @Inject + DataCenterDao _dcDao; + @Inject + HostDao _hostDao; + @Inject + AgentManager _agentMgr; + @Inject + ResourceManager _resourceMgr; + @Inject + List _services; + @Inject + UserVmDao _vmDao; + @Inject + ServiceOfferingDao _serviceOfferingDao; + @Inject + NicDao _nicDao; + @Inject + ConfigurationDao _configDao; + @Inject + PhysicalNetworkDao _phynwDao; + @Inject + NetworkModel _ntwkModel; + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + setName(name); + _resourceMgr.registerResourceStateAdapter(this.getClass().getSimpleName(), this); + return true; + } + + @Override + public boolean start() { + return true; + } + + @Override + public boolean stop() { + _resourceMgr.unregisterResourceStateAdapter(this.getClass().getSimpleName()); + return true; + } + + protected BaremetalPxeService getServiceByType(String type) { + for (BaremetalPxeService service : _services) { + if (service.getPxeServiceType().equals(type)) { + return service; + } + } + + throw new CloudRuntimeException("Cannot find PXE service for " + type); + } + + @Override + public boolean prepare(VirtualMachineProfile profile, NicProfile nic, Network network, DeployDestination dest, ReservationContext context) { + //TODO: select type from template + BaremetalPxeType type = BaremetalPxeType.KICK_START; + return getServiceByType(type.toString()).prepare(profile, nic, network, dest, context); + } + + @Override + public boolean prepareCreateTemplate(Long pxeServerId, UserVm vm, String templateUrl) { + //TODO: select type from template + BaremetalPxeType type = BaremetalPxeType.PING; + return getServiceByType(type.toString()).prepareCreateTemplate(pxeServerId, vm, templateUrl); + } + + @Override + public BaremetalPxeType getPxeServerType(HostVO host) { + if (host.getResource().equalsIgnoreCase(BaremetalPingPxeResource.class.getName())) { + return BaremetalPxeType.PING; + } else { + throw new CloudRuntimeException("Unkown PXE server resource " + host.getResource()); + } + } + + @Override + public HostVO createHostVOForConnectedAgent(HostVO host, StartupCommand[] cmd) { + // TODO Auto-generated method stub + return null; + } + + @Override + public HostVO createHostVOForDirectConnectAgent(HostVO host, StartupCommand[] startup, ServerResource resource, Map details, List hostTags) { + if (!(startup[0] instanceof StartupPxeServerCommand)) { + return null; + } + + host.setType(Host.Type.BaremetalPxe); + return host; + } + + @Override + public DeleteHostAnswer deleteHost(HostVO host, boolean isForced, boolean isForceDeleteStorage) throws UnableDeleteHostException { + // TODO Auto-generated method stub + return null; + } + + @Override + public BaremetalPxeVO addPxeServer(AddBaremetalPxeCmd cmd) { + return getServiceByType(cmd.getDeviceType()).addPxeServer(cmd); + } + + @Override + public BaremetalPxeResponse getApiResponse(BaremetalPxeVO vo) { + return getServiceByType(vo.getDeviceType()).getApiResponse(vo); + } + + @Override + public List listPxeServers(ListBaremetalPxeServersCmd cmd) { + return getServiceByType(BaremetalPxeType.KICK_START.toString()).listPxeServers(cmd); + } + + @Override + public boolean addUserData(NicProfile nic, VirtualMachineProfile profile) { + UserVmVO vm = _vmDao.findById(profile.getVirtualMachine().getId()); + _vmDao.loadDetails(vm); + + String serviceOffering = _serviceOfferingDao.findByIdIncludingRemoved(vm.getId(), vm.getServiceOfferingId()).getDisplayText(); + String zoneName = _dcDao.findById(vm.getDataCenterId()).getName(); + NicVO nvo = _nicDao.findById(nic.getId()); + VmDataCommand cmd = new VmDataCommand(nvo.getIPv4Address(), vm.getInstanceName(), _ntwkModel.getExecuteInSeqNtwkElmtCmd()); + // if you add new metadata files, also edit systemvm/patches/debian/config/var/www/html/latest/.htaccess + cmd.addVmData("userdata", "user-data", vm.getUserData()); + cmd.addVmData("metadata", "service-offering", StringUtils.unicodeEscape(serviceOffering)); + cmd.addVmData("metadata", "availability-zone", StringUtils.unicodeEscape(zoneName)); + cmd.addVmData("metadata", "local-ipv4", nic.getIPv4Address()); + cmd.addVmData("metadata", "local-hostname", StringUtils.unicodeEscape(vm.getInstanceName())); + cmd.addVmData("metadata", "public-ipv4", nic.getIPv4Address()); + cmd.addVmData("metadata", "public-hostname", StringUtils.unicodeEscape(vm.getInstanceName())); + cmd.addVmData("metadata", "instance-id", String.valueOf(vm.getId())); + cmd.addVmData("metadata", "vm-id", String.valueOf(vm.getInstanceName())); + cmd.addVmData("metadata", "public-keys", null); + String cloudIdentifier = _configDao.getValue("cloud.identifier"); + if (cloudIdentifier == null) { + cloudIdentifier = ""; + } else { + cloudIdentifier = "CloudStack-{" + cloudIdentifier + "}"; + } + cmd.addVmData("metadata", "cloud-identifier", cloudIdentifier); + + List phys = _phynwDao.listByZone(vm.getDataCenterId()); + if (phys.isEmpty()) { + throw new CloudRuntimeException(String.format("Cannot find physical network in zone %s", vm.getDataCenterId())); + } + if (phys.size() > 1) { + throw new CloudRuntimeException(String.format("Baremetal only supports one physical network in zone, but zone %s has %s physical networks", + vm.getDataCenterId(), phys.size())); + } + PhysicalNetworkVO phy = phys.get(0); + + QueryBuilder sc = QueryBuilder.create(BaremetalPxeVO.class); + //TODO: handle both kickstart and PING + //sc.addAnd(sc.getEntity().getPodId(), Op.EQ, vm.getPodIdToDeployIn()); + sc.and(sc.entity().getPhysicalNetworkId(), Op.EQ, phy.getId()); + BaremetalPxeVO pxeVo = sc.find(); + if (pxeVo == null) { + throw new CloudRuntimeException("No PXE server found in pod: " + vm.getPodIdToDeployIn() + ", you need to add it before starting VM"); + } + + try { + Answer ans = _agentMgr.send(pxeVo.getHostId(), cmd); + if (!ans.getResult()) { + s_logger.debug(String.format("Add userdata to vm:%s failed because %s", vm.getInstanceName(), ans.getDetails())); + return false; + } else { + return true; + } + } catch (Exception e) { + s_logger.debug(String.format("Add userdata to vm:%s failed", vm.getInstanceName()), e); + return false; + } + } + + @Override + public List> getCommands() { + List> cmds = new ArrayList>(); + cmds.add(AddBaremetalKickStartPxeCmd.class); + cmds.add(AddBaremetalPxePingServerCmd.class); + cmds.add(ListBaremetalPxeServersCmd.class); + return cmds; + } +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxePingResponse.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxePingResponse.java new file mode 100644 index 0000000000..50b554f252 --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxePingResponse.java @@ -0,0 +1,65 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +package com.cloud.baremetal.networkservice; + +import com.cloud.baremetal.database.BaremetalPxeVO; +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.EntityReference; + +@EntityReference(value = BaremetalPxeVO.class) +public class BaremetalPxePingResponse extends BaremetalPxeResponse { + @SerializedName(ApiConstants.PING_STORAGE_SERVER_IP) + @Param(description = "PING storage server ip") + private String pingStorageServerIp; + + @SerializedName(ApiConstants.PING_DIR) + @Param(description = "Root directory on PING storage server") + private String pingDir; + + @SerializedName(ApiConstants.TFTP_DIR) + @Param(description = "Tftp root directory of PXE server") + private String tftpDir; + + public String getPingStorageServerIp() { + return pingStorageServerIp; + } + + public void setPingStorageServerIp(String pingStorageServerIp) { + this.pingStorageServerIp = pingStorageServerIp; + } + + public String getPingDir() { + return pingDir; + } + + public void setPingDir(String pingDir) { + this.pingDir = pingDir; + } + + public String getTftpDir() { + return tftpDir; + } + + public void setTftpDir(String tftpDir) { + this.tftpDir = tftpDir; + } +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeResourceBase.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeResourceBase.java new file mode 100644 index 0000000000..d03ccbe81a --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeResourceBase.java @@ -0,0 +1,159 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +// Apache License, Version 2.0 (the "License"); you may not use this +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// Automatically generated by addcopyright.py at 04/03/2012 +package com.cloud.baremetal.networkservice; + +import java.util.Map; + +import javax.naming.ConfigurationException; + +import com.cloud.agent.IAgentControl; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.PingCommand; +import com.cloud.agent.api.ReadyAnswer; +import com.cloud.agent.api.ReadyCommand; +import com.cloud.agent.api.StartupCommand; +import com.cloud.agent.api.StartupPxeServerCommand; +import com.cloud.host.Host.Type; +import com.cloud.resource.ServerResource; +import com.cloud.utils.component.ManagerBase; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BaremetalPxeResourceBase extends ManagerBase implements ServerResource { + private static final Logger s_logger = LoggerFactory.getLogger(BaremetalPxeResourceBase.class); + String _name; + String _guid; + String _username; + String _password; + String _ip; + String _zoneId; + String _podId; + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + _name = name; + _guid = (String)params.get(BaremetalPxeService.PXE_PARAM_GUID); + _ip = (String)params.get(BaremetalPxeService.PXE_PARAM_IP); + _username = (String)params.get(BaremetalPxeService.PXE_PARAM_USERNAME); + _password = (String)params.get(BaremetalPxeService.PXE_PARAM_PASSWORD); + _zoneId = (String)params.get(BaremetalPxeService.PXE_PARAM_ZONE); + _podId = (String)params.get(BaremetalPxeService.PXE_PARAM_POD); + + if (_guid == null) { + throw new ConfigurationException("No Guid specified"); + } + + if (_zoneId == null) { + throw new ConfigurationException("No Zone specified"); + } + + if (_ip == null) { + throw new ConfigurationException("No IP specified"); + } + + if (_username == null) { + throw new ConfigurationException("No username specified"); + } + + if (_password == null) { + throw new ConfigurationException("No password specified"); + } + + return true; + } + + protected ReadyAnswer execute(ReadyCommand cmd) { + s_logger.debug("Pxe resource " + _name + " is ready"); + return new ReadyAnswer(cmd); + } + + @Override + public boolean start() { + return true; + } + + @Override + public boolean stop() { + return true; + } + + @Override + public String getName() { + // TODO Auto-generated method stub + return _name; + } + + @Override + public Type getType() { + return Type.BaremetalPxe; + } + + @Override + public StartupCommand[] initialize() { + StartupPxeServerCommand cmd = new StartupPxeServerCommand(); + cmd.setName(_name); + cmd.setDataCenter(_zoneId); + cmd.setPod(_podId); + cmd.setPrivateIpAddress(_ip); + cmd.setStorageIpAddress(""); + cmd.setVersion(""); + cmd.setGuid(_guid); + return new StartupCommand[] {cmd}; + } + + @Override + public PingCommand getCurrentStatus(long id) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void disconnected() { + // TODO Auto-generated method stub + + } + + @Override + public IAgentControl getAgentControl() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setAgentControl(IAgentControl agentControl) { + // TODO Auto-generated method stub + + } + + @Override + public Answer executeRequest(Command cmd) { + if (cmd instanceof ReadyCommand) { + return execute((ReadyCommand)cmd); + } else { + return Answer.createUnsupportedCommandAnswer(cmd); + } + } + +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeResponse.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeResponse.java new file mode 100644 index 0000000000..50fe17940e --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeResponse.java @@ -0,0 +1,75 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +package com.cloud.baremetal.networkservice; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +public class BaremetalPxeResponse extends BaseResponse { + @SerializedName(ApiConstants.ID) + @Param(description = "device id of ") + private String id; + + @SerializedName(ApiConstants.PHYSICAL_NETWORK_ID) + @Param(description = "the physical network to which this external dhcp device belongs to") + private String physicalNetworkId; + + @SerializedName(ApiConstants.PROVIDER) + @Param(description = "name of the provider") + private String providerId; + + @SerializedName(ApiConstants.URL) + @Param(description = "url") + private String url; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getPhysicalNetworkId() { + return physicalNetworkId; + } + + public void setPhysicalNetworkId(String physicalNetworkId) { + this.physicalNetworkId = physicalNetworkId; + } + + public String getProviderId() { + return providerId; + } + + public void setProviderId(String providerId) { + this.providerId = providerId; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeService.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeService.java new file mode 100644 index 0000000000..014da7a0b4 --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeService.java @@ -0,0 +1,65 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +// Apache License, Version 2.0 (the "License"); you may not use this +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// Automatically generated by addcopyright.py at 04/03/2012 +package com.cloud.baremetal.networkservice; + +import java.util.List; + +import com.cloud.baremetal.database.BaremetalPxeVO; +import com.cloud.deploy.DeployDestination; +import com.cloud.network.Network; +import com.cloud.uservm.UserVm; +import com.cloud.utils.component.Adapter; +import com.cloud.vm.NicProfile; +import com.cloud.vm.ReservationContext; +import com.cloud.vm.VirtualMachineProfile; + +import org.apache.cloudstack.api.AddBaremetalPxeCmd; +import org.apache.cloudstack.api.ListBaremetalPxeServersCmd; + +public interface BaremetalPxeService extends Adapter { + + public boolean prepare(VirtualMachineProfile profile, NicProfile nic, Network network, DeployDestination dest, ReservationContext context); + + public boolean prepareCreateTemplate(Long pxeServerId, UserVm vm, String templateUrl); + + BaremetalPxeVO addPxeServer(AddBaremetalPxeCmd cmd); + + BaremetalPxeResponse getApiResponse(BaremetalPxeVO vo); + + List listPxeServers(ListBaremetalPxeServersCmd cmd); + + String getPxeServiceType(); + + public static final String PXE_PARAM_TYPE = "type"; + public static final String PXE_PARAM_ZONE = "zone"; + public static final String PXE_PARAM_POD = "pod"; + public static final String PXE_PARAM_IP = "ip"; + public static final String PXE_PARAM_GUID = "guid"; + public static final String PXE_PARAM_TFTP_DIR = "tftpDir"; + public static final String PXE_PARAM_USERNAME = "username"; + public static final String PXE_PARAM_PASSWORD = "password"; + public static final String PXE_PARAM_PING_STORAGE_SERVER_IP = "pingStorageServerIp"; + public static final String PXE_PARAM_PING_ROOT_DIR = "pingDir"; + public static final String PXE_PARAM_PING_STORAGE_SERVER_USERNAME = "pingStorageServerUserName"; + public static final String PXE_PARAM_PING_STORAGE_SERVER_PASSWORD = "pingStorageServerPassword"; +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalRctResponse.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalRctResponse.java new file mode 100644 index 0000000000..9a0b38d19d --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalRctResponse.java @@ -0,0 +1,56 @@ +// 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.baremetal.networkservice; + +import com.cloud.baremetal.database.BaremetalRctVO; +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; + +/** + * Created by frank on 5/8/14. + */ +@EntityReference(value = BaremetalRctVO.class) +public class BaremetalRctResponse extends BaseResponse { + @SerializedName(ApiConstants.ID) + @Param(description = "id of rct") + private String id; + + @SerializedName(ApiConstants.URL) + @Param(description = "url") + private String url; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/Force10BaremetalSwitchBackend.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/Force10BaremetalSwitchBackend.java new file mode 100644 index 0000000000..819f573aaa --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/Force10BaremetalSwitchBackend.java @@ -0,0 +1,254 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +// Apache License, Version 2.0 (the "License"); you may not use this +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// Automatically generated by addcopyright.py at 04/03/2012 +package com.cloud.baremetal.networkservice; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.xmlobject.XmlObject; +import com.cloud.utils.xmlobject.XmlObjectParser; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.web.client.ResponseErrorHandler; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +/** + * Created by frank on 9/2/14. + */ +public class Force10BaremetalSwitchBackend implements BaremetalSwitchBackend { + private Logger logger = LoggerFactory.getLogger(Force10BaremetalSwitchBackend.class); + public static final String TYPE = "Force10"; + + private static List successHttpStatusCode = new ArrayList<>(); + { + successHttpStatusCode.add(HttpStatus.OK); + successHttpStatusCode.add(HttpStatus.ACCEPTED); + successHttpStatusCode.add(HttpStatus.CREATED); + successHttpStatusCode.add(HttpStatus.NO_CONTENT); + successHttpStatusCode.add(HttpStatus.PARTIAL_CONTENT); + successHttpStatusCode.add(HttpStatus.RESET_CONTENT); + successHttpStatusCode.add(HttpStatus.ALREADY_REPORTED); + } + + RestTemplate rest = new RestTemplate(); + { + // fake error handler, we handle error in business logic code + rest.setErrorHandler(new ResponseErrorHandler() { + @Override + public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException { + return false; + } + + @Override + public void handleError(ClientHttpResponse clientHttpResponse) throws IOException { + } + }); + } + + private String buildLink(String switchIp, String path) { + UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); + builder.scheme("http"); + builder.host(switchIp); + builder.port(8008); + builder.path(path); + return builder.build().toUriString(); + } + + @Override + public String getSwitchBackendType() { + return TYPE; + } + + @Override + public void prepareVlan(BaremetalVlanStruct struct) { + String link = buildLink(struct.getSwitchIp(), String.format("/api/running/ftos/interface/vlan/%s", struct.getVlan())); + HttpHeaders headers = createBasicAuthenticationHeader(struct); + HttpEntity request = new HttpEntity<>(headers); + ResponseEntity rsp = rest.exchange(link, HttpMethod.GET, request, String.class); + logger.debug(String.format("http get: %s", link)); + + if (rsp.getStatusCode() == HttpStatus.NOT_FOUND) { + PortInfo port = new PortInfo(struct); + XmlObject xml = new XmlObject("vlan").putElement("vlan-id", + new XmlObject("vlan-id").setText(String.valueOf(struct.getVlan()))).putElement("untagged", + new XmlObject("untagged").putElement(port.interfaceType, new XmlObject(port.interfaceType) + .putElement("name", new XmlObject("name").setText(port.port))) + ).putElement("shutdown", new XmlObject("shutdown").setText("false")); + request = new HttpEntity<>(xml.dump(), headers); + link = buildLink(struct.getSwitchIp(), String.format("/api/running/ftos/interface/")); + logger.debug(String.format("http get: %s, body: %s", link, request)); + rsp = rest.exchange(link, HttpMethod.POST, request, String.class); + if (!successHttpStatusCode.contains(rsp.getStatusCode())) { + throw new CloudRuntimeException(String.format("unable to create vlan[%s] on force10 switch[ip:%s]. HTTP status code:%s, body dump:%s", + struct.getVlan(), struct.getSwitchIp(),rsp.getStatusCode(), rsp.getBody())); + } else { + logger.debug(String.format("successfully programmed vlan[%s] on Force10[ip:%s, port:%s]. http response[status code:%s, body:%s]", + struct.getVlan(), struct.getSwitchIp(), struct.getPort(), rsp.getStatusCode(), rsp.getBody())); + } + } else if (successHttpStatusCode.contains(rsp.getStatusCode())) { + PortInfo port = new PortInfo(struct); + XmlObject xml = XmlObjectParser.parseFromString((String)rsp.getBody()); + List ports = xml.getAsList("untagged.tengigabitethernet"); + ports.addAll(xml.getAsList("untagged.gigabitethernet")); + ports.addAll(xml.getAsList("untagged.fortyGigE")); + for (XmlObject pxml : ports) { + XmlObject name = pxml.get("name"); + if (port.port.equals(name.getText())) { + logger.debug(String.format("port[%s] has joined in vlan[%s], no need to program again", struct.getPort(), struct.getVlan())); + return; + } + } + + xml.removeElement("mtu"); + xml.setText(null); + XmlObject tag = xml.get("untagged"); + if (tag == null) { + tag = new XmlObject("untagged"); + xml.putElement("untagged", tag); + } + + tag.putElement(port.interfaceType, new XmlObject(port.interfaceType) + .putElement("name", new XmlObject("name").setText(port.port))); + request = new HttpEntity<>(xml.dump(), headers); + link = buildLink(struct.getSwitchIp(), String.format("/api/running/ftos/interface/vlan/%s", struct.getVlan())); + logger.debug(String.format("http get: %s, body: %s", link, request)); + rsp = rest.exchange(link, HttpMethod.PUT, request, String.class); + if (!successHttpStatusCode.contains(rsp.getStatusCode())) { + throw new CloudRuntimeException(String.format("failed to program vlan[%s] for port[%s] on force10[ip:%s]. http status:%s, body dump:%s", + struct.getVlan(), struct.getPort(), struct.getSwitchIp(), rsp.getStatusCode(), rsp.getBody())); + } else { + logger.debug(String.format("successfully join port[%s] into vlan[%s] on Force10[ip:%s]. http response[status code:%s, body:%s]", + struct.getPort(), struct.getVlan(), struct.getSwitchIp(), rsp.getStatusCode(), rsp.getBody())); + } + } else { + throw new CloudRuntimeException(String.format("force10[ip:%s] returns unexpected error[%s] when http getting %s, body dump:%s", + struct.getSwitchIp(), rsp.getStatusCode(), link, rsp.getBody())); + } + } + + @Override + public void removePortFromVlan(BaremetalVlanStruct struct) { + String link = buildLink(struct.getSwitchIp(), String.format("/api/running/ftos/interface/vlan/%s", struct.getVlan())); + HttpHeaders headers = createBasicAuthenticationHeader(struct); + HttpEntity request = new HttpEntity<>(headers); + logger.debug(String.format("http get: %s, body: %s", link, request)); + ResponseEntity rsp = rest.exchange(link, HttpMethod.GET, request, String.class); + if (rsp.getStatusCode() == HttpStatus.NOT_FOUND) { + logger.debug(String.format("vlan[%s] has been deleted on force10[ip:%s], no need to remove the port[%s] anymore", struct.getVlan(), struct.getSwitchIp(), struct.getPort())); + } else if (rsp.getStatusCode() == HttpStatus.OK) { + PortInfo port = new PortInfo(struct); + XmlObject xml = XmlObjectParser.parseFromString((String)rsp.getBody()); + List ports = xml.getAsList("untagged.tengigabitethernet"); + ports.addAll(xml.getAsList("untagged.gigabitethernet")); + ports.addAll(xml.getAsList("untagged.fortyGigE")); + List newPorts = new ArrayList<>(); + boolean needRemove = false; + for (XmlObject pxml : ports) { + XmlObject name = pxml.get("name"); + if (port.port.equals(name.getText())) { + needRemove = true; + continue; + } + + newPorts.add(pxml); + } + + if (!needRemove) { + return; + } + + xml.setText(null); + xml.removeElement("mtu"); + XmlObject tagged = xml.get("untagged"); + tagged.removeAllChildren(); + for (XmlObject p : newPorts) { + tagged.putElement(p.getTag(), p); + } + + + request = new HttpEntity<>(xml.dump(), headers); + logger.debug(String.format("http get: %s, body: %s", link, request)); + rsp = rest.exchange(link, HttpMethod.PUT, request, String.class); + if (!successHttpStatusCode.contains(rsp.getStatusCode())) { + throw new CloudRuntimeException(String.format("failed to program vlan[%s] for port[%s] on force10[ip:%s]. http status:%s, body dump:%s", + struct.getVlan(), struct.getPort(), struct.getSwitchIp(), rsp.getStatusCode(), rsp.getBody())); + } else { + logger.debug(String.format("removed port[%s] from vlan[%s] on force10[ip:%s]", struct.getPort(), struct.getVlan(), struct.getSwitchIp())); + } + } else { + throw new CloudRuntimeException(String.format("force10[ip:%s] returns unexpected error[%s] when http getting %s, body dump:%s", + struct.getSwitchIp(), rsp.getStatusCode(), link, rsp.getBody())); + } + } + + private HttpHeaders createBasicAuthenticationHeader(BaremetalVlanStruct struct) { + String plainCreds = String.format("%s:%s", struct.getSwitchUsername(), struct.getSwitchPassword()); + byte[] plainCredsBytes = plainCreds.getBytes(); + byte[] base64CredsBytes = Base64.encodeBase64(plainCredsBytes); + String base64Creds = new String(base64CredsBytes); + HttpHeaders headers = new HttpHeaders(); + headers.add("Authorization", "Basic " + base64Creds); + headers.setAccept(Arrays.asList(MediaType.ALL)); + headers.setContentType(MediaType.valueOf("application/vnd.yang.data+xml")); + return headers; + } + + private class PortInfo { + static final String G_IFACE = "gigabitethernet"; + static final String TEN_G_IFACE = "tengigabitethernet"; + static final String FOURTY_G_IFACE = "fortyGigE"; + + private String interfaceType; + private String port; + + PortInfo(BaremetalVlanStruct struct) { + String[] ps = StringUtils.split(struct.getPort(), ":"); + if (ps.length == 1) { + interfaceType = TEN_G_IFACE; + port = ps[0]; + } else if (ps.length == 2) { + interfaceType = ps[0]; + if (!interfaceType.equals(G_IFACE) && !interfaceType.equals(TEN_G_IFACE) && !interfaceType.equals(FOURTY_G_IFACE)) { + throw new CloudRuntimeException(String.format("wrong port definition[%s]. The prefix must be one of [%s,%s,%s]", struct.getPort(), G_IFACE, TEN_G_IFACE, FOURTY_G_IFACE)); + } + port = ps[1]; + } else { + throw new CloudRuntimeException(String.format("wrong port definition[%s]. Force10 port should be in format of interface_type:port_identity, for example: tengigabitethernet:1/3", struct.getPort())); + } + } + } +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/SecurityGroupHttpClient.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/SecurityGroupHttpClient.java new file mode 100644 index 0000000000..e97f77cd2d --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/SecurityGroupHttpClient.java @@ -0,0 +1,208 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +// Apache License, Version 2.0 (the "License"); you may not use this +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// Automatically generated by addcopyright.py at 04/03/2012 + +package com.cloud.baremetal.networkservice; + +import java.io.StringWriter; +import java.net.SocketTimeoutException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Marshaller; + +import com.cloud.agent.api.SecurityGroupRuleAnswer; +import com.cloud.agent.api.SecurityGroupRulesCmd; +import com.cloud.agent.api.SecurityGroupRulesCmd.IpPortAndProto; +import com.cloud.baremetal.networkservice.schema.SecurityGroupRule; +import com.cloud.baremetal.networkservice.schema.SecurityGroupVmRuleSet; +import com.cloud.utils.Pair; +import com.cloud.utils.exception.CloudRuntimeException; + +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.methods.StringRequestEntity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SecurityGroupHttpClient { + private static final Logger logger = LoggerFactory.getLogger(SecurityGroupHttpClient.class); + private static final String ARG_NAME = "args"; + private static final String COMMAND = "command"; + private JAXBContext context; + private int port; + private static HttpClient httpClient; + static { + MultiThreadedHttpConnectionManager connman = new MultiThreadedHttpConnectionManager(); + httpClient = new HttpClient(connman); + httpClient.setConnectionTimeout(5000); + } + + private enum OpConstant { + setRules, echo, + } + + public SecurityGroupHttpClient() { + try { + context = JAXBContext.newInstance(SecurityGroupRule.class, SecurityGroupVmRuleSet.class); + port = 9988; + } catch (Exception e) { + throw new CloudRuntimeException( + "Unable to create JAXBContext for security group", e); + } + } + + private List generateRules(IpPortAndProto[] ipps) { + List rules = new ArrayList( + ipps.length); + for (SecurityGroupRulesCmd.IpPortAndProto ipp : ipps) { + SecurityGroupRule r = new SecurityGroupRule(); + r.setProtocol(ipp.getProto()); + r.setStartPort(ipp.getStartPort()); + r.setEndPort(ipp.getEndPort()); + for (String cidr : ipp.getAllowedCidrs()) { + r.getIp().add(cidr); + } + rules.add(r); + } + return rules; + } + + public HashMap> sync(String vmName, Long vmId, String agentIp) { + HashMap> states = new HashMap>(); + PostMethod post = new PostMethod(String.format("http://%s:%s/", agentIp, getPort())); + try { + post.addRequestHeader("command", "sync"); + if (httpClient.executeMethod(post) != 200) { + logger.debug(String.format("echoing baremetal security group agent on %s got error: %s", agentIp, post.getResponseBodyAsString())); + } else { + String res = post.getResponseBodyAsString(); + // res = ';'.join([vmName, vmId, seqno]) + String[] rulelogs = res.split(","); + if (rulelogs.length != 6) { + logger.debug(String.format("host[%s] returns invalid security group sync document[%s], reset rules", agentIp, res)); + states.put(vmName, new Pair(vmId, -1L)); + return states; + } + Pair p = new Pair(Long.valueOf(rulelogs[1]), Long.valueOf(rulelogs[5])); + states.put(rulelogs[0], p); + return states; + } + } catch (SocketTimeoutException se) { + logger.warn(String.format("unable to sync security group rules on host[%s], %s", agentIp, se.getMessage())); + } catch (Exception e) { + logger.warn(String.format("unable to sync security group rules on host[%s]", agentIp), e); + } finally { + if (post != null) { + post.releaseConnection(); + } + } + return states; + } + + + public boolean echo(String agentIp, long l, long m) { + boolean ret = false; + int count = 1; + while (true) { + try { + Thread.sleep(m); + count++; + } catch (InterruptedException e1) { + logger.warn("", e1); + break; + } + PostMethod post = new PostMethod(String.format("http://%s:%s/", agentIp, getPort())); + try { + post.addRequestHeader("command", "echo"); + if (httpClient.executeMethod(post) != 200) { + logger.debug(String.format("echoing baremetal security group agent on %s got error: %s", agentIp, post.getResponseBodyAsString())); + } else { + ret = true; + } + break; + } catch (Exception e) { + if (count*m >= l) { + logger.debug(String.format("ping security group agent on vm[%s] timeout after %s minutes, starting vm failed, count=%s", agentIp, TimeUnit.MILLISECONDS.toSeconds(l), count)); + break; + } else { + logger.debug(String.format("Having pinged security group agent on vm[%s] %s times, continue to wait...", agentIp, count)); + } + } finally { + if (post != null) { + post.releaseConnection(); + } + } + } + return ret; + } + + public SecurityGroupRuleAnswer call(String agentIp, SecurityGroupRulesCmd cmd) { + PostMethod post = new PostMethod(String.format( + "http://%s:%s", agentIp, getPort())); + try { + SecurityGroupVmRuleSet rset = new SecurityGroupVmRuleSet(); + rset.getEgressRules().addAll(generateRules(cmd.getEgressRuleSet())); + rset.getIngressRules().addAll( + generateRules(cmd.getIngressRuleSet())); + rset.setVmName(cmd.getVmName()); + rset.setVmIp(cmd.getGuestIp()); + rset.setVmMac(cmd.getGuestMac()); + rset.setVmId(cmd.getVmId()); + rset.setSignature(cmd.getSignature()); + rset.setSequenceNumber(cmd.getSeqNum()); + Marshaller marshaller = context.createMarshaller(); + StringWriter writer = new StringWriter(); + marshaller.marshal(rset, writer); + String xmlContents = writer.toString(); + logger.debug(xmlContents); + + post.addRequestHeader("command", "set_rules"); + StringRequestEntity entity = new StringRequestEntity(xmlContents); + post.setRequestEntity(entity); + if (httpClient.executeMethod(post) != 200) { + return new SecurityGroupRuleAnswer(cmd, false, + post.getResponseBodyAsString()); + } else { + return new SecurityGroupRuleAnswer(cmd); + } + } catch (Exception e) { + return new SecurityGroupRuleAnswer(cmd, false, e.getMessage()); + } finally { + if (post != null) { + post.releaseConnection(); + } + } + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/schema/SecurityGroupRule.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/schema/SecurityGroupRule.java new file mode 100644 index 0000000000..4e30606362 --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/schema/SecurityGroupRule.java @@ -0,0 +1,156 @@ +// 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.baremetal.networkservice.schema; + +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlSchemaType; +import javax.xml.bind.annotation.XmlType; + + +/** + *

    Java class for SecurityGroupRule complex type. + * + *

    The following schema fragment specifies the expected content contained within this class. + * + *

    + * <complexType name="SecurityGroupRule">
    + *   <complexContent>
    + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
    + *       <sequence>
    + *         <element name="protocol" type="{http://www.w3.org/2001/XMLSchema}string"/>
    + *         <element name="startPort" type="{http://www.w3.org/2001/XMLSchema}unsignedInt"/>
    + *         <element name="endPort" type="{http://www.w3.org/2001/XMLSchema}unsignedInt"/>
    + *         <sequence maxOccurs="unbounded" minOccurs="0">
    + *           <element name="ip" type="{http://www.w3.org/2001/XMLSchema}string"/>
    + *         </sequence>
    + *       </sequence>
    + *     </restriction>
    + *   </complexContent>
    + * </complexType>
    + * 
    + * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "SecurityGroupRule", propOrder = { + "protocol", + "startPort", + "endPort", + "ip" +}) +public class SecurityGroupRule { + + @XmlElement(required = true) + protected String protocol; + @XmlSchemaType(name = "unsignedInt") + protected long startPort; + @XmlSchemaType(name = "unsignedInt") + protected long endPort; + protected List ip; + + /** + * Gets the value of the protocol property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getProtocol() { + return protocol; + } + + /** + * Sets the value of the protocol property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setProtocol(String value) { + this.protocol = value; + } + + /** + * Gets the value of the startPort property. + * + */ + public long getStartPort() { + return startPort; + } + + /** + * Sets the value of the startPort property. + * + */ + public void setStartPort(long value) { + this.startPort = value; + } + + /** + * Gets the value of the endPort property. + * + */ + public long getEndPort() { + return endPort; + } + + /** + * Sets the value of the endPort property. + * + */ + public void setEndPort(long value) { + this.endPort = value; + } + + /** + * Gets the value of the ip property. + * + *

    + * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the ip property. + * + *

    + * For example, to add a new item, do as follows: + *

    +     *    getIp().add(newItem);
    +     * 
    + * + * + *

    + * Objects of the following type(s) are allowed in the list + * {@link String } + * + * + */ + public List getIp() { + if (ip == null) { + ip = new ArrayList(); + } + return this.ip; + } + +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/schema/SecurityGroupVmRuleSet.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/schema/SecurityGroupVmRuleSet.java new file mode 100644 index 0000000000..ff9042a1b6 --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/schema/SecurityGroupVmRuleSet.java @@ -0,0 +1,264 @@ +// +// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.1.10 +// See http://java.sun.com/xml/jaxb +// Any modifications to this file will be lost upon recompilation of the source schema. +// Generated on: 2012.07.11 at 03:24:15 PM PDT +// + + +package com.cloud.baremetal.networkservice.schema; + +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; + + +/** + *

    Java class for anonymous complex type. + * + *

    The following schema fragment specifies the expected content contained within this class. + * + *

    + * <complexType>
    + *   <complexContent>
    + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
    + *       <sequence>
    + *         <element name="vmName" type="{http://www.w3.org/2001/XMLSchema}string"/>
    + *         <element name="vmId" type="{http://www.w3.org/2001/XMLSchema}long"/>
    + *         <element name="vmIp" type="{http://www.w3.org/2001/XMLSchema}string"/>
    + *         <element name="vmMac" type="{http://www.w3.org/2001/XMLSchema}string"/>
    + *         <element name="signature" type="{http://www.w3.org/2001/XMLSchema}string"/>
    + *         <element name="sequenceNumber" type="{http://www.w3.org/2001/XMLSchema}long"/>
    + *         <sequence maxOccurs="unbounded" minOccurs="0">
    + *           <element name="ingressRules" type="{}SecurityGroupRule"/>
    + *         </sequence>
    + *         <sequence maxOccurs="unbounded" minOccurs="0">
    + *           <element name="egressRules" type="{}SecurityGroupRule"/>
    + *         </sequence>
    + *       </sequence>
    + *     </restriction>
    + *   </complexContent>
    + * </complexType>
    + * 
    + * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "", propOrder = { + "vmName", + "vmId", + "vmIp", + "vmMac", + "signature", + "sequenceNumber", + "ingressRules", + "egressRules" +}) +@XmlRootElement(name = "SecurityGroupVmRuleSet") +public class SecurityGroupVmRuleSet { + + @XmlElement(required = true) + protected String vmName; + protected long vmId; + @XmlElement(required = true) + protected String vmIp; + @XmlElement(required = true) + protected String vmMac; + @XmlElement(required = true) + protected String signature; + protected long sequenceNumber; + protected List ingressRules; + protected List egressRules; + + /** + * Gets the value of the vmName property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getVmName() { + return vmName; + } + + /** + * Sets the value of the vmName property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setVmName(String value) { + this.vmName = value; + } + + /** + * Gets the value of the vmId property. + * + */ + public long getVmId() { + return vmId; + } + + /** + * Sets the value of the vmId property. + * + */ + public void setVmId(long value) { + this.vmId = value; + } + + /** + * Gets the value of the vmIp property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getVmIp() { + return vmIp; + } + + /** + * Sets the value of the vmIp property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setVmIp(String value) { + this.vmIp = value; + } + + /** + * Gets the value of the vmMac property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getVmMac() { + return vmMac; + } + + /** + * Sets the value of the vmMac property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setVmMac(String value) { + this.vmMac = value; + } + + /** + * Gets the value of the signature property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getSignature() { + return signature; + } + + /** + * Sets the value of the signature property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setSignature(String value) { + this.signature = value; + } + + /** + * Gets the value of the sequenceNumber property. + * + */ + public long getSequenceNumber() { + return sequenceNumber; + } + + /** + * Sets the value of the sequenceNumber property. + * + */ + public void setSequenceNumber(long value) { + this.sequenceNumber = value; + } + + /** + * Gets the value of the ingressRules property. + * + *

    + * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the ingressRules property. + * + *

    + * For example, to add a new item, do as follows: + *

    +     *    getIngressRules().add(newItem);
    +     * 
    + * + * + *

    + * Objects of the following type(s) are allowed in the list + * {@link SecurityGroupRule } + * + * + */ + public List getIngressRules() { + if (ingressRules == null) { + ingressRules = new ArrayList(); + } + return this.ingressRules; + } + + /** + * Gets the value of the egressRules property. + * + *

    + * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the egressRules property. + * + *

    + * For example, to add a new item, do as follows: + *

    +     *    getEgressRules().add(newItem);
    +     * 
    + * + * + *

    + * Objects of the following type(s) are allowed in the list + * {@link SecurityGroupRule } + * + * + */ + public List getEgressRules() { + if (egressRules == null) { + egressRules = new ArrayList(); + } + return this.egressRules; + } + +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalDhcpCmd.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalDhcpCmd.java new file mode 100644 index 0000000000..84b9de3b95 --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalDhcpCmd.java @@ -0,0 +1,142 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +package org.apache.cloudstack.api; + +import javax.inject.Inject; + +import com.cloud.baremetal.database.BaremetalDhcpVO; +import com.cloud.baremetal.networkservice.BaremetalDhcpManager; +import com.cloud.baremetal.networkservice.BaremetalDhcpResponse; +import com.cloud.event.EventTypes; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; + +import org.apache.cloudstack.api.response.PhysicalNetworkResponse; +import org.apache.cloudstack.context.CallContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@APICommand(name = "addBaremetalDhcp", description = "adds a baremetal dhcp server", responseObject = BaremetalDhcpResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class AddBaremetalDhcpCmd extends BaseAsyncCmd { + private static final String s_name = "addbaremetaldhcpresponse"; + public static final Logger s_logger = LoggerFactory.getLogger(AddBaremetalDhcpCmd.class); + + @Inject + BaremetalDhcpManager mgr; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + @Parameter(name = ApiConstants.PHYSICAL_NETWORK_ID, + type = CommandType.UUID, + entityType = PhysicalNetworkResponse.class, + required = true, + description = "the Physical Network ID") + private Long physicalNetworkId; + + @Parameter(name = ApiConstants.DHCP_SERVER_TYPE, type = CommandType.STRING, required = true, description = "Type of dhcp device") + private String dhcpType; + + @Parameter(name = ApiConstants.URL, type = CommandType.STRING, required = true, description = "URL of the external dhcp appliance.") + private String url; + + @Parameter(name = ApiConstants.USERNAME, type = CommandType.STRING, required = true, description = "Credentials to reach external dhcp device") + private String username; + + @Parameter(name = ApiConstants.PASSWORD, type = CommandType.STRING, required = true, description = "Credentials to reach external dhcp device") + private String password; + + @Override + public String getEventType() { + return EventTypes.EVENT_BAREMETAL_DHCP_SERVER_ADD; + } + + @Override + public String getEventDescription() { + return "Adding an external DHCP server"; + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, + ResourceAllocationException, NetworkRuleConflictException { + try { + BaremetalDhcpVO vo = mgr.addDchpServer(this); + BaremetalDhcpResponse response = mgr.generateApiResponse(vo); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } catch (Exception e) { + s_logger.warn("Unable to add external dhcp server with url: " + getUrl(), e); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + public String getDhcpType() { + return dhcpType; + } + + public void setDhcpType(String dhcpType) { + this.dhcpType = dhcpType; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Long getPhysicalNetworkId() { + return physicalNetworkId; + } + + public void setPhysicalNetworkId(Long physicalNetworkId) { + this.physicalNetworkId = physicalNetworkId; + } +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalHostCmd.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalHostCmd.java new file mode 100644 index 0000000000..821619c4a8 --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalHostCmd.java @@ -0,0 +1,49 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +package org.apache.cloudstack.api; + +import com.cloud.baremetal.manager.BareMetalDiscoverer; + +import org.apache.cloudstack.api.command.admin.host.AddHostCmd; +import org.apache.cloudstack.api.response.HostResponse; + +@APICommand(name = "addBaremetalHost", description = "add a baremetal host", responseObject = HostResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class AddBaremetalHostCmd extends AddHostCmd { + + @Parameter(name = ApiConstants.IP_ADDRESS, type = CommandType.STRING, description = "ip address intentionally allocated to this host after provisioning") + private String vmIpAddress; + + public AddBaremetalHostCmd() { + } + + @Override + public void execute() { + this.getFullUrlParams().put(ApiConstants.BAREMETAL_DISCOVER_NAME, BareMetalDiscoverer.class.getName()); + super.execute(); + } + + public String getVmIpAddress() { + return vmIpAddress; + } + + public void setVmIpAddress(String vmIpAddress) { + this.vmIpAddress = vmIpAddress; + } +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalPxeCmd.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalPxeCmd.java new file mode 100644 index 0000000000..f1a05bf0f4 --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalPxeCmd.java @@ -0,0 +1,151 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +package org.apache.cloudstack.api; + +import javax.inject.Inject; + +import com.cloud.baremetal.database.BaremetalPxeVO; +import com.cloud.baremetal.networkservice.BaremetalPxeManager; +import com.cloud.baremetal.networkservice.BaremetalPxeResponse; +import com.cloud.event.EventTypes; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; + +import org.apache.cloudstack.api.response.PhysicalNetworkResponse; +import org.apache.cloudstack.api.response.PodResponse; +import org.apache.cloudstack.context.CallContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AddBaremetalPxeCmd extends BaseAsyncCmd { + private static final String s_name = "addbaremetalpxeresponse"; + public static final Logger s_logger = LoggerFactory.getLogger(AddBaremetalPxeCmd.class); + + @Inject + BaremetalPxeManager pxeMgr; + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + @Parameter(name = ApiConstants.PHYSICAL_NETWORK_ID, + type = CommandType.UUID, + entityType = PhysicalNetworkResponse.class, + required = true, + description = "the Physical Network ID") + private Long physicalNetworkId; + + @Parameter(name = ApiConstants.POD_ID, type = CommandType.UUID, entityType = PodResponse.class, description = "Pod Id") + private Long podId; + + @Parameter(name = ApiConstants.URL, type = CommandType.STRING, required = true, description = "URL of the external pxe device") + private String url; + + @Parameter(name = ApiConstants.PXE_SERVER_TYPE, type = CommandType.STRING, required = true, description = "type of pxe device") + private String deviceType; + + @Parameter(name = ApiConstants.USERNAME, type = CommandType.STRING, required = true, description = "Credentials to reach external pxe device") + private String username; + + @Parameter(name = ApiConstants.PASSWORD, type = CommandType.STRING, required = true, description = "Credentials to reach external pxe device") + private String password; + + @Override + public String getEventType() { + return EventTypes.EVENT_BAREMETAL_PXE_SERVER_ADD; + } + + @Override + public String getEventDescription() { + return "Adding an external pxe server"; + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, + ResourceAllocationException, NetworkRuleConflictException { + try { + BaremetalPxeVO vo = pxeMgr.addPxeServer(this); + BaremetalPxeResponse rsp = pxeMgr.getApiResponse(vo); + rsp.setResponseName(getCommandName()); + this.setResponseObject(rsp); + } catch (Exception e) { + s_logger.warn("Unable to add external pxe server with url: " + getUrl(), e); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + public Long getPhysicalNetworkId() { + return physicalNetworkId; + } + + public void setPhysicalNetworkId(Long physicalNetworkId) { + this.physicalNetworkId = physicalNetworkId; + } + + public Long getPodId() { + return podId; + } + + public void setPodId(Long podId) { + this.podId = podId; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getDeviceType() { + return deviceType; + } + + public void setDeviceType(String deviceType) { + this.deviceType = deviceType; + } +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalRctCmd.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalRctCmd.java new file mode 100644 index 0000000000..ce20f69b3a --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalRctCmd.java @@ -0,0 +1,88 @@ +// 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 org.apache.cloudstack.api; + +import javax.inject.Inject; + +import com.cloud.baremetal.manager.BaremetalVlanManager; +import com.cloud.baremetal.networkservice.BaremetalRctResponse; +import com.cloud.event.EventTypes; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.context.CallContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Created by frank on 5/8/14. + */ +@APICommand(name = "addBaremetalRct", description = "adds baremetal rack configuration text", responseObject = BaremetalRctResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.Admin}) +public class AddBaremetalRctCmd extends BaseAsyncCmd { + private static final String s_name = "addbaremetalrctresponse"; + public static final Logger s_logger = LoggerFactory.getLogger(AddBaremetalRctCmd.class); + + @Inject + private BaremetalVlanManager vlanMgr; + + @Parameter(name=ApiConstants.BAREMETAL_RCT_URL, required = true, description = "http url to baremetal RCT configuration") + private String rctUrl; + + public String getRctUrl() { + return rctUrl; + } + + public void setRctUrl(String rctUrl) { + this.rctUrl = rctUrl; + } + + public String getEventType() { + return EventTypes.EVENT_BAREMETAL_RCT_ADD; + } + + @Override + public String getEventDescription() { + return "Adding baremetal rct configuration"; + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try { + BaremetalRctResponse rsp = vlanMgr.addRct(this); + this.setResponseObject(rsp); + } catch (Exception e) { + s_logger.warn(String.format("unable to add baremetal RCT[%s]", getRctUrl()), e); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/BaremetalProvisionDoneNotificationCmd.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/BaremetalProvisionDoneNotificationCmd.java new file mode 100644 index 0000000000..ba5df912ec --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/BaremetalProvisionDoneNotificationCmd.java @@ -0,0 +1,89 @@ +// 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 org.apache.cloudstack.api; + +import javax.inject.Inject; + +import com.cloud.baremetal.manager.BaremetalManager; +import com.cloud.event.EventTypes; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Created by frank on 9/17/14. + */ +@APICommand(name = "notifyBaremetalProvisionDone", description = "Notify provision has been done on a host. This api is for baremetal virtual router service, not for end user", responseObject = SuccessResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.User}) +public class BaremetalProvisionDoneNotificationCmd extends BaseAsyncCmd { + public static final Logger s_logger = LoggerFactory.getLogger(BaremetalProvisionDoneNotificationCmd.class); + private static final String s_name = "baremetalprovisiondone"; + + @Inject + private BaremetalManager bmMgr; + + @Parameter(name="mac", required = true, description = "mac of the nic used for provision") + private String mac; + + @Override + public String getEventType() { + return EventTypes.EVENT_BAREMETAL_PROVISION_DONE; + } + + @Override + public String getEventDescription() { + return "notify management server that baremetal provision has been done on a host"; + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try { + bmMgr.notifyProvisionDone(this); + this.setResponseObject(new SuccessResponse(getCommandName())); + } catch (Exception e) { + s_logger.warn(String.format("unable to notify baremetal provision done[mac:%s]", mac), e); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + public String getMac() { + return mac; + } + + public void setMac(String mac) { + this.mac = mac; + } + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/DeleteBaremetalRctCmd.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/DeleteBaremetalRctCmd.java new file mode 100644 index 0000000000..bbe5b1b96d --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/DeleteBaremetalRctCmd.java @@ -0,0 +1,87 @@ +// 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 org.apache.cloudstack.api; + +import javax.inject.Inject; + +import com.cloud.baremetal.manager.BaremetalVlanManager; +import com.cloud.baremetal.networkservice.BaremetalRctResponse; +import com.cloud.event.EventTypes; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Created by frank on 10/27/14. + */ +@APICommand(name = "deleteBaremetalRct", description = "deletes baremetal rack configuration text", responseObject = SuccessResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.Admin}) +public class DeleteBaremetalRctCmd extends BaseAsyncCmd { + private static final String s_name = "deletebaremetalrctresponse"; + public static final Logger s_logger = LoggerFactory.getLogger(DeleteBaremetalRctCmd.class); + + @Parameter(name = ApiConstants.ID, type = BaseCmd.CommandType.UUID, description = "RCT id", required = true, entityType = BaremetalRctResponse.class) + private Long id; + + @Inject + private BaremetalVlanManager vlanMgr; + + @Override + public String getEventType() { + return EventTypes.EVENT_BAREMETAL_RCT_DELETE; + } + + @Override + public String getEventDescription() { + return "Deleting baremetal rct configuration"; + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try { + vlanMgr.deleteRct(this); + SuccessResponse response = new SuccessResponse(getCommandName()); + setResponseObject(response); + } catch (Exception e) { + s_logger.warn(String.format("unable to add baremetal RCT[%s]", getId()), e); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + public Long getId() { + return id; + } +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/ListBaremetalDhcpCmd.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/ListBaremetalDhcpCmd.java new file mode 100644 index 0000000000..44bac6141b --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/ListBaremetalDhcpCmd.java @@ -0,0 +1,108 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +package org.apache.cloudstack.api; + +import java.util.List; + +import javax.inject.Inject; + +import com.cloud.baremetal.networkservice.BaremetalDhcpManager; +import com.cloud.baremetal.networkservice.BaremetalDhcpResponse; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; + +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.PhysicalNetworkResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@APICommand(name = "listBaremetalDhcp", description = "list baremetal dhcp servers", responseObject = BaremetalDhcpResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class ListBaremetalDhcpCmd extends BaseListCmd { + private static final Logger s_logger = LoggerFactory.getLogger(ListBaremetalDhcpCmd.class); + private static final String s_name = "listbaremetaldhcpresponse"; + @Inject + BaremetalDhcpManager _dhcpMgr; + + // /////////////////////////////////////////////////// + // ////////////// API parameters ///////////////////// + // /////////////////////////////////////////////////// + @Parameter(name = ApiConstants.ID, type = CommandType.LONG, description = "DHCP server device ID") + private Long id; + + @Parameter(name = ApiConstants.DHCP_SERVER_TYPE, type = CommandType.STRING, description = "Type of DHCP device") + private String deviceType; + + @Parameter(name = ApiConstants.PHYSICAL_NETWORK_ID, + type = CommandType.UUID, + entityType = PhysicalNetworkResponse.class, + required = true, + description = "the Physical Network ID") + private Long physicalNetworkId; + + public Long getPhysicalNetworkId() { + return physicalNetworkId; + } + + public void setPhysicalNetworkId(Long physicalNetworkId) { + this.physicalNetworkId = physicalNetworkId; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getDeviceType() { + return deviceType; + } + + public void setDeviceType(String deviceType) { + this.deviceType = deviceType; + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, + ResourceAllocationException, NetworkRuleConflictException { + try { + ListResponse response = new ListResponse(); + List dhcpResponses = _dhcpMgr.listBaremetalDhcps(this); + response.setResponses(dhcpResponses); + response.setResponseName(getCommandName()); + response.setObjectName("baremetaldhcps"); + this.setResponseObject(response); + } catch (Exception e) { + s_logger.debug("Exception happend while executing ListBaremetalDhcpCmd"); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + + } + + @Override + public String getCommandName() { + return s_name; + } + +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/ListBaremetalPxeServersCmd.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/ListBaremetalPxeServersCmd.java new file mode 100644 index 0000000000..596cca22b7 --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/ListBaremetalPxeServersCmd.java @@ -0,0 +1,92 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +package org.apache.cloudstack.api; + +import java.util.List; + +import javax.inject.Inject; + +import com.cloud.baremetal.networkservice.BaremetalPxeManager; +import com.cloud.baremetal.networkservice.BaremetalPxeResponse; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; + +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.PhysicalNetworkResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@APICommand(name = "listBaremetalPxeServers", description = "list baremetal pxe server", responseObject = BaremetalPxeResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class ListBaremetalPxeServersCmd extends BaseListCmd { + private static final Logger s_logger = LoggerFactory.getLogger(ListBaremetalPxeServersCmd.class); + private static final String s_name = "listbaremetalpxeserversresponse"; + + @Inject + BaremetalPxeManager _pxeMgr; + // /////////////////////////////////////////////////// + // ////////////// API parameters ///////////////////// + // /////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.LONG, description = "Pxe server device ID") + private Long id; + + @Parameter(name = ApiConstants.PHYSICAL_NETWORK_ID, + type = CommandType.UUID, + entityType = PhysicalNetworkResponse.class, + required = true, + description = "the Physical Network ID") + private Long physicalNetworkId; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getPhysicalNetworkId() { + return physicalNetworkId; + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, + ResourceAllocationException, NetworkRuleConflictException { + try { + ListResponse response = new ListResponse(); + List pxeResponses = _pxeMgr.listPxeServers(this); + response.setResponses(pxeResponses); + response.setResponseName(getCommandName()); + response.setObjectName("baremetalpxeservers"); + this.setResponseObject(response); + } catch (Exception e) { + s_logger.debug("Exception happened while executing ListPingPxeServersCmd", e); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public String getCommandName() { + return s_name; + } +} diff --git a/cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/ListBaremetalRctCmd.java b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/ListBaremetalRctCmd.java new file mode 100644 index 0000000000..bab53a19e2 --- /dev/null +++ b/cosmic-core/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/ListBaremetalRctCmd.java @@ -0,0 +1,72 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +package org.apache.cloudstack.api; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import com.cloud.baremetal.manager.BaremetalVlanManager; +import com.cloud.baremetal.networkservice.BaremetalRctResponse; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.response.ListResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@APICommand(name = "listBaremetalRct", description = "list baremetal rack configuration", responseObject = BaremetalRctResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.Admin}) +public class ListBaremetalRctCmd extends BaseListCmd { + private static final Logger s_logger = LoggerFactory.getLogger(ListBaremetalRctCmd.class); + private static final String s_name = "listbaremetalrctresponse"; + @Inject + BaremetalVlanManager vlanMgr; + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, + ResourceAllocationException, NetworkRuleConflictException { + try { + ListResponse response = new ListResponse<>(); + List rctResponses = new ArrayList<>(); + BaremetalRctResponse rsp = vlanMgr.listRct(); + if (rsp != null) { + rctResponses.add(rsp); + } + response.setResponses(rctResponses); + response.setResponseName(getCommandName()); + response.setObjectName("baremetalrcts"); + this.setResponseObject(response); + } catch (Exception e) { + s_logger.debug("Exception happened while executing ListBaremetalRctCmd", e); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public String getCommandName() { + return s_name; + } + +} diff --git a/cosmic-core/plugins/network-elements/internal-loadbalancer/pom.xml b/cosmic-core/plugins/network-elements/internal-loadbalancer/pom.xml new file mode 100644 index 0000000000..138e5341e5 --- /dev/null +++ b/cosmic-core/plugins/network-elements/internal-loadbalancer/pom.xml @@ -0,0 +1,14 @@ + + 4.0.0 + cloud-plugin-network-internallb + Cosmic Plugin - Network Internal Load Balancer + + cloud.cosmic + cosmic-plugins + 5.1.0.0-SNAPSHOT + ../../pom.xml + + + install + + diff --git a/cosmic-core/plugins/network-elements/internal-loadbalancer/src/main/java/org/apache/cloudstack/network/element/InternalLoadBalancerElement.java b/cosmic-core/plugins/network-elements/internal-loadbalancer/src/main/java/org/apache/cloudstack/network/element/InternalLoadBalancerElement.java new file mode 100644 index 0000000000..5f6e45a7b3 --- /dev/null +++ b/cosmic-core/plugins/network-elements/internal-loadbalancer/src/main/java/org/apache/cloudstack/network/element/InternalLoadBalancerElement.java @@ -0,0 +1,513 @@ +// 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 org.apache.cloudstack.network.element; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.inject.Inject; + +import com.cloud.agent.api.to.LoadBalancerTO; +import com.cloud.configuration.ConfigurationManager; +import com.cloud.dc.DataCenter; +import com.cloud.dc.DataCenter.NetworkType; +import com.cloud.deploy.DeployDestination; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.IllegalVirtualMachineException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Network; +import com.cloud.network.Network.Capability; +import com.cloud.network.Network.Provider; +import com.cloud.network.Network.Service; +import com.cloud.network.NetworkModel; +import com.cloud.network.Networks.TrafficType; +import com.cloud.network.PhysicalNetworkServiceProvider; +import com.cloud.network.PublicIpAddress; +import com.cloud.network.VirtualRouterProvider; +import com.cloud.network.VirtualRouterProvider.Type; +import com.cloud.network.dao.NetworkServiceMapDao; +import com.cloud.network.dao.PhysicalNetworkServiceProviderDao; +import com.cloud.network.dao.VirtualRouterProviderDao; +import com.cloud.network.element.IpDeployer; +import com.cloud.network.element.LoadBalancingServiceProvider; +import com.cloud.network.element.VirtualRouterElement; +import com.cloud.network.element.VirtualRouterProviderVO; +import com.cloud.network.lb.LoadBalancingRule; +import com.cloud.network.router.VirtualRouter; +import com.cloud.network.router.VirtualRouter.Role; +import com.cloud.network.rules.LoadBalancerContainer; +import com.cloud.network.rules.LoadBalancerContainer.Scheme; +import com.cloud.offering.NetworkOffering; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.User; +import com.cloud.utils.component.AdapterBase; +import com.cloud.utils.db.EntityManager; +import com.cloud.utils.db.QueryBuilder; +import com.cloud.utils.db.SearchCriteria.Op; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.Ip; +import com.cloud.vm.DomainRouterVO; +import com.cloud.vm.NicProfile; +import com.cloud.vm.ReservationContext; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.DomainRouterDao; + +import org.apache.cloudstack.api.command.admin.internallb.ConfigureInternalLoadBalancerElementCmd; +import org.apache.cloudstack.api.command.admin.internallb.CreateInternalLoadBalancerElementCmd; +import org.apache.cloudstack.api.command.admin.internallb.ListInternalLoadBalancerElementsCmd; +import org.apache.cloudstack.lb.dao.ApplicationLoadBalancerRuleDao; +import org.apache.cloudstack.network.lb.InternalLoadBalancerVMManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class InternalLoadBalancerElement extends AdapterBase implements LoadBalancingServiceProvider, InternalLoadBalancerElementService, IpDeployer { + private static final Logger s_logger = LoggerFactory.getLogger(InternalLoadBalancerElement.class); + protected static final Map> capabilities = setCapabilities(); + private static InternalLoadBalancerElement internalLbElement = null; + + @Inject + NetworkModel _ntwkModel; + @Inject + NetworkServiceMapDao _ntwkSrvcDao; + @Inject + DomainRouterDao _routerDao; + @Inject + VirtualRouterProviderDao _vrProviderDao; + @Inject + PhysicalNetworkServiceProviderDao _pNtwkSvcProviderDao; + @Inject + InternalLoadBalancerVMManager _internalLbMgr; + @Inject + ConfigurationManager _configMgr; + @Inject + AccountManager _accountMgr; + @Inject + ApplicationLoadBalancerRuleDao _appLbDao; + @Inject + EntityManager _entityMgr; + + protected InternalLoadBalancerElement() { + } + + public static InternalLoadBalancerElement getInstance() { + if (internalLbElement == null) { + internalLbElement = new InternalLoadBalancerElement(); + } + return internalLbElement; + } + + private boolean canHandle(Network config, Scheme lbScheme) { + //works in Advance zone only + DataCenter dc = _entityMgr.findById(DataCenter.class, config.getDataCenterId()); + if (dc.getNetworkType() != NetworkType.Advanced) { + s_logger.trace("Not hanling zone of network type " + dc.getNetworkType()); + return false; + } + if (config.getGuestType() != Network.GuestType.Isolated || config.getTrafficType() != TrafficType.Guest) { + s_logger.trace("Not handling network with Type " + config.getGuestType() + " and traffic type " + config.getTrafficType()); + return false; + } + + Map lbCaps = getCapabilities().get(Service.Lb); + if (!lbCaps.isEmpty()) { + String schemeCaps = lbCaps.get(Capability.LbSchemes); + if (schemeCaps != null && lbScheme != null) { + if (!schemeCaps.contains(lbScheme.toString())) { + s_logger.debug("Scheme " + lbScheme.toString() + " is not supported by the provider " + getName()); + return false; + } + } + } + + if (!_ntwkModel.isProviderSupportServiceInNetwork(config.getId(), Service.Lb, getProvider())) { + s_logger.trace("Element " + getProvider().getName() + " doesn't support service " + Service.Lb + " in the network " + config); + return false; + } + return true; + } + + @Override + public Map> getCapabilities() { + return capabilities; + } + + @Override + public Provider getProvider() { + return Provider.InternalLbVm; + } + + @Override + public boolean implement(Network network, NetworkOffering offering, DeployDestination dest, ReservationContext context) throws ConcurrentOperationException, + ResourceUnavailableException, InsufficientCapacityException { + + if (!canHandle(network, null)) { + s_logger.trace("No need to implement " + getName()); + return true; + } + + return implementInternalLbVms(network, dest); + } + + @Override + public boolean prepare(Network network, NicProfile nic, VirtualMachineProfile vm, DeployDestination dest, ReservationContext context) + throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException, IllegalVirtualMachineException { + + if (!canHandle(network, null)) { + s_logger.trace("No need to prepare " + getName()); + return true; + } + + if (vm.getType() == VirtualMachine.Type.User) { + return implementInternalLbVms(network, dest); + } + return true; + } + + protected boolean implementInternalLbVms(Network network, DeployDestination dest) throws ResourceUnavailableException { + //1) Get all the Ips from the network having LB rules assigned + List ips = _appLbDao.listLbIpsBySourceIpNetworkIdAndScheme(network.getId(), Scheme.Internal); + + //2) Start internal lb vms for the ips having active rules + for (String ip : ips) { + Ip sourceIp = new Ip(ip); + long active = _appLbDao.countActiveBySourceIp(sourceIp, network.getId()); + if (active > 0) { + s_logger.debug("Have to implement internal lb vm for source ip " + sourceIp + " as a part of network " + network + " implement as there are " + active + + " internal lb rules exist for this ip"); + List internalLbVms; + try { + internalLbVms = _internalLbMgr.deployInternalLbVm(network, sourceIp, dest, _accountMgr.getAccount(network.getAccountId()), null); + } catch (InsufficientCapacityException e) { + s_logger.warn("Failed to deploy element " + getName() + " for ip " + sourceIp + " due to:", e); + return false; + } catch (ConcurrentOperationException e) { + s_logger.warn("Failed to deploy element " + getName() + " for ip " + sourceIp + " due to:", e); + return false; + } + + if (internalLbVms == null || internalLbVms.isEmpty()) { + throw new ResourceUnavailableException("Can't deploy " + getName() + " to handle LB rules", DataCenter.class, network.getDataCenterId()); + } + } + } + + return true; + } + + @Override + public boolean release(Network network, NicProfile nic, VirtualMachineProfile vm, ReservationContext context) throws ConcurrentOperationException, + ResourceUnavailableException { + return true; + } + + @Override + public boolean shutdown(Network network, ReservationContext context, boolean cleanup) throws ConcurrentOperationException, ResourceUnavailableException { + List internalLbVms = _routerDao.listByNetworkAndRole(network.getId(), Role.INTERNAL_LB_VM); + if (internalLbVms == null || internalLbVms.isEmpty()) { + return true; + } + boolean result = true; + for (VirtualRouter internalLbVm : internalLbVms) { + result = result && _internalLbMgr.destroyInternalLbVm(internalLbVm.getId(), context.getAccount(), context.getCaller().getId()); + if (cleanup) { + if (!result) { + s_logger.warn("Failed to stop internal lb element " + internalLbVm + ", but would try to process clean up anyway."); + } + result = (_internalLbMgr.destroyInternalLbVm(internalLbVm.getId(), context.getAccount(), context.getCaller().getId())); + if (!result) { + s_logger.warn("Failed to clean up internal lb element " + internalLbVm); + } + } + } + return result; + } + + @Override + public boolean destroy(Network network, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException { + List internalLbVms = _routerDao.listByNetworkAndRole(network.getId(), Role.INTERNAL_LB_VM); + if (internalLbVms == null || internalLbVms.isEmpty()) { + return true; + } + boolean result = true; + for (VirtualRouter internalLbVm : internalLbVms) { + result = result && (_internalLbMgr.destroyInternalLbVm(internalLbVm.getId(), context.getAccount(), context.getCaller().getId())); + } + return result; + } + + @Override + public boolean isReady(PhysicalNetworkServiceProvider provider) { + VirtualRouterProviderVO element = _vrProviderDao.findByNspIdAndType(provider.getId(), Type.InternalLbVm); + if (element == null) { + return false; + } + return element.isEnabled(); + } + + @Override + public boolean shutdownProviderInstances(PhysicalNetworkServiceProvider provider, ReservationContext context) throws ConcurrentOperationException, + ResourceUnavailableException { + VirtualRouterProviderVO element = _vrProviderDao.findByNspIdAndType(provider.getId(), Type.InternalLbVm); + if (element == null) { + return true; + } + long elementId = element.getId(); + List internalLbVms = _routerDao.listByElementId(elementId); + boolean result = true; + for (DomainRouterVO internalLbVm : internalLbVms) { + result = result && (_internalLbMgr.destroyInternalLbVm(internalLbVm.getId(), context.getAccount(), context.getCaller().getId())); + } + _vrProviderDao.remove(elementId); + + return result; + } + + @Override + public boolean canEnableIndividualServices() { + return true; + } + + @Override + public boolean verifyServicesCombination(Set services) { + return true; + } + + @Override + public IpDeployer getIpDeployer(Network network) { + return this; + } + + @Override + public boolean applyLBRules(Network network, List rules) throws ResourceUnavailableException { + //1) Get Internal LB VMs to destroy + Set vmsToDestroy = getVmsToDestroy(rules); + + //2) Get rules to apply + Map> rulesToApply = getLbRulesToApply(rules); + s_logger.debug("Applying " + rulesToApply.size() + " on element " + getName()); + + for (Ip sourceIp : rulesToApply.keySet()) { + if (vmsToDestroy.contains(sourceIp)) { + //2.1 Destroy internal lb vm + List vms = _internalLbMgr.findInternalLbVms(network.getId(), sourceIp); + if (vms.size() > 0) { + //only one internal lb per IP exists + try { + s_logger.debug("Destroying internal lb vm for ip " + sourceIp.addr() + " as all the rules for this vm are in Revoke state"); + return _internalLbMgr.destroyInternalLbVm(vms.get(0).getId(), _accountMgr.getAccount(Account.ACCOUNT_ID_SYSTEM), + _accountMgr.getUserIncludingRemoved(User.UID_SYSTEM).getId()); + } catch (ConcurrentOperationException e) { + s_logger.warn("Failed to apply lb rule(s) for ip " + sourceIp.addr() + " on the element " + getName() + " due to:", e); + return false; + } + } + } else { + //2.2 Start Internal LB vm per IP address + List internalLbVms; + try { + DeployDestination dest = new DeployDestination(_entityMgr.findById(DataCenter.class, network.getDataCenterId()), null, null, null); + internalLbVms = _internalLbMgr.deployInternalLbVm(network, sourceIp, dest, _accountMgr.getAccount(network.getAccountId()), null); + } catch (InsufficientCapacityException e) { + s_logger.warn("Failed to apply lb rule(s) for ip " + sourceIp.addr() + "on the element " + getName() + " due to:", e); + return false; + } catch (ConcurrentOperationException e) { + s_logger.warn("Failed to apply lb rule(s) for ip " + sourceIp.addr() + "on the element " + getName() + " due to:", e); + return false; + } + + if (internalLbVms == null || internalLbVms.isEmpty()) { + throw new ResourceUnavailableException("Can't find/deploy internal lb vm to handle LB rules", DataCenter.class, network.getDataCenterId()); + } + + //2.3 Apply Internal LB rules on the VM + if (!_internalLbMgr.applyLoadBalancingRules(network, rulesToApply.get(sourceIp), internalLbVms)) { + throw new CloudRuntimeException("Failed to apply load balancing rules for ip " + sourceIp.addr() + " in network " + network.getId() + " on element " + + getName()); + } + } + } + + return true; + } + + protected Map> getLbRulesToApply(List rules) { + //Group rules by the source ip address as NetworkManager always passes the entire network lb config to the element + Map> rulesToApply = groupBySourceIp(rules); + + return rulesToApply; + } + + protected Set getVmsToDestroy(List rules) { + //1) Group rules by the source ip address as NetworkManager always passes the entire network lb config to the element + Map> groupedRules = groupBySourceIp(rules); + + Set vmsToDestroy = new HashSet(); + + for (Ip sourceIp : groupedRules.keySet()) { + //2) Check if there are non revoked rules for the source ip address + List rulesToCheck = groupedRules.get(sourceIp); + if (_appLbDao.countBySourceIpAndNotRevoked(sourceIp, rulesToCheck.get(0).getNetworkId()) == 0) { + s_logger.debug("Have to destroy internal lb vm for source ip " + sourceIp + " as it has 0 rules in non-Revoke state"); + vmsToDestroy.add(sourceIp); + } + } + return vmsToDestroy; + } + + protected Map> groupBySourceIp(List rules) { + Map> groupedRules = new HashMap>(); + for (LoadBalancingRule rule : rules) { + if (rule.getDestinations() != null && !rule.getDestinations().isEmpty()) { + Ip sourceIp = rule.getSourceIp(); + if (!groupedRules.containsKey(sourceIp)) { + groupedRules.put(sourceIp, null); + } + + List rulesToApply = groupedRules.get(sourceIp); + if (rulesToApply == null) { + rulesToApply = new ArrayList(); + } + rulesToApply.add(rule); + groupedRules.put(sourceIp, rulesToApply); + } else { + s_logger.debug("Internal lb rule " + rule + " doesn't have any vms assigned, skipping"); + } + } + return groupedRules; + } + + @Override + public boolean validateLBRule(Network network, LoadBalancingRule rule) { + List rules = new ArrayList(); + rules.add(rule); + if (canHandle(network, rule.getScheme())) { + List routers = _routerDao.listByNetworkAndRole(network.getId(), Role.INTERNAL_LB_VM); + if (routers == null || routers.isEmpty()) { + return true; + } + return VirtualRouterElement.validateHAProxyLBRule(rule); + } + return true; + } + + @Override + public List updateHealthChecks(Network network, List lbrules) { + return null; + } + + private static Map> setCapabilities() { + Map> capabilities = new HashMap>(); + + // Set capabilities for LB service + Map lbCapabilities = new HashMap(); + lbCapabilities.put(Capability.SupportedLBAlgorithms, "roundrobin,leastconn,source"); + lbCapabilities.put(Capability.SupportedLBIsolation, "dedicated"); + lbCapabilities.put(Capability.SupportedProtocols, "tcp, udp"); + lbCapabilities.put(Capability.SupportedStickinessMethods, VirtualRouterElement.getHAProxyStickinessCapability()); + lbCapabilities.put(Capability.LbSchemes, LoadBalancerContainer.Scheme.Internal.toString()); + + capabilities.put(Service.Lb, lbCapabilities); + return capabilities; + } + + @Override + public List> getCommands() { + List> cmdList = new ArrayList>(); + cmdList.add(CreateInternalLoadBalancerElementCmd.class); + cmdList.add(ConfigureInternalLoadBalancerElementCmd.class); + cmdList.add(ListInternalLoadBalancerElementsCmd.class); + return cmdList; + } + + @Override + public VirtualRouterProvider configureInternalLoadBalancerElement(long id, boolean enable) { + VirtualRouterProviderVO element = _vrProviderDao.findById(id); + if (element == null || element.getType() != Type.InternalLbVm) { + throw new InvalidParameterValueException("Can't find " + getName() + " element with network service provider id " + id + " to be used as a provider for " + + getName()); + } + + element.setEnabled(enable); + element = _vrProviderDao.persist(element); + + return element; + } + + @Override + public VirtualRouterProvider addInternalLoadBalancerElement(long ntwkSvcProviderId) { + VirtualRouterProviderVO element = _vrProviderDao.findByNspIdAndType(ntwkSvcProviderId, Type.InternalLbVm); + if (element != null) { + s_logger.debug("There is already an " + getName() + " with service provider id " + ntwkSvcProviderId); + return null; + } + + PhysicalNetworkServiceProvider provider = _pNtwkSvcProviderDao.findById(ntwkSvcProviderId); + if (provider == null || !provider.getProviderName().equalsIgnoreCase(getName())) { + throw new InvalidParameterValueException("Invalid network service provider is specified"); + } + + element = new VirtualRouterProviderVO(ntwkSvcProviderId, Type.InternalLbVm); + element = _vrProviderDao.persist(element); + return element; + } + + @Override + public VirtualRouterProvider getInternalLoadBalancerElement(long id) { + VirtualRouterProvider provider = _vrProviderDao.findById(id); + if (provider == null || provider.getType() != Type.InternalLbVm) { + throw new InvalidParameterValueException("Unable to find " + getName() + " by id"); + } + return provider; + } + + @Override + public List searchForInternalLoadBalancerElements(Long id, Long ntwkSvsProviderId, Boolean enabled) { + + QueryBuilder sc = QueryBuilder.create(VirtualRouterProviderVO.class); + if (id != null) { + sc.and(sc.entity().getId(), Op.EQ, id); + } + if (ntwkSvsProviderId != null) { + sc.and(sc.entity().getNspId(), Op.EQ, ntwkSvsProviderId); + } + if (enabled != null) { + sc.and(sc.entity().isEnabled(), Op.EQ, enabled); + } + + //return only Internal LB elements + sc.and(sc.entity().getType(), Op.EQ, VirtualRouterProvider.Type.InternalLbVm); + + return sc.list(); + } + + @Override + public boolean applyIps(Network network, List ipAddress, Set services) throws ResourceUnavailableException { + //do nothing here; this element just has to extend the ip deployer + //as the LB service implements IPDeployerRequester + return true; + } + +} diff --git a/cosmic-core/plugins/network-elements/internal-loadbalancer/src/main/java/org/apache/cloudstack/network/lb/InternalLoadBalancerVMManager.java b/cosmic-core/plugins/network-elements/internal-loadbalancer/src/main/java/org/apache/cloudstack/network/lb/InternalLoadBalancerVMManager.java new file mode 100644 index 0000000000..339b0c1c3f --- /dev/null +++ b/cosmic-core/plugins/network-elements/internal-loadbalancer/src/main/java/org/apache/cloudstack/network/lb/InternalLoadBalancerVMManager.java @@ -0,0 +1,82 @@ +// 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 org.apache.cloudstack.network.lb; + +import java.util.List; +import java.util.Map; + +import com.cloud.deploy.DeployDestination; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Network; +import com.cloud.network.lb.LoadBalancingRule; +import com.cloud.network.router.VirtualRouter; +import com.cloud.user.Account; +import com.cloud.utils.net.Ip; +import com.cloud.vm.VirtualMachineProfile.Param; + +public interface InternalLoadBalancerVMManager { + //RAM/CPU for the system offering used by Internal LB VMs + public static final int DEFAULT_INTERNALLB_VM_RAMSIZE = 256; // 256 MB + public static final int DEFAULT_INTERNALLB_VM_CPU_MHZ = 256; // 256 MHz + + /** + * Destroys Internal LB vm instance + * @param vmId + * @param caller + * @param callerUserId + * @return + * @throws ResourceUnavailableException + * @throws ConcurrentOperationException + */ + boolean destroyInternalLbVm(long vmId, Account caller, Long callerUserId) throws ResourceUnavailableException, ConcurrentOperationException; + + /** + * Deploys internal lb vm + * @param guestNetwork + * @param requestedGuestIp + * @param dest + * @param owner + * @param params + * @return + * @throws InsufficientCapacityException + * @throws ConcurrentOperationException + * @throws ResourceUnavailableException + */ + List deployInternalLbVm(Network guestNetwork, Ip requestedGuestIp, DeployDestination dest, Account owner, Map params) + throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException; + + /** + * + * @param network + * @param rules + * @param internalLbVms + * @return + * @throws ResourceUnavailableException + */ + boolean applyLoadBalancingRules(Network network, List rules, List internalLbVms) throws ResourceUnavailableException; + + /** + * Returns existing Internal Load Balancer elements based on guestNetworkId (required) and requestedIp (optional) + * @param guestNetworkId + * @param requestedGuestIp + * @return + */ + List findInternalLbVms(long guestNetworkId, Ip requestedGuestIp); + +} diff --git a/cosmic-core/plugins/network-elements/internal-loadbalancer/src/main/java/org/apache/cloudstack/network/lb/InternalLoadBalancerVMManagerImpl.java b/cosmic-core/plugins/network-elements/internal-loadbalancer/src/main/java/org/apache/cloudstack/network/lb/InternalLoadBalancerVMManagerImpl.java new file mode 100644 index 0000000000..d37892ef01 --- /dev/null +++ b/cosmic-core/plugins/network-elements/internal-loadbalancer/src/main/java/org/apache/cloudstack/network/lb/InternalLoadBalancerVMManagerImpl.java @@ -0,0 +1,915 @@ +// 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 org.apache.cloudstack.network.lb; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.GetDomRVersionAnswer; +import com.cloud.agent.api.GetDomRVersionCmd; +import com.cloud.agent.api.check.CheckSshAnswer; +import com.cloud.agent.api.check.CheckSshCommand; +import com.cloud.agent.api.routing.LoadBalancerConfigCommand; +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.api.to.LoadBalancerTO; +import com.cloud.agent.manager.Commands; +import com.cloud.configuration.Config; +import com.cloud.configuration.ConfigurationManagerImpl; +import com.cloud.dc.DataCenter; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.deploy.DataCenterDeployment; +import com.cloud.deploy.DeployDestination; +import com.cloud.deploy.DeploymentPlan; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientAddressCapacityException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InsufficientServerCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.exception.StorageUnavailableException; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.network.IpAddressManager; +import com.cloud.network.Network; +import com.cloud.network.Network.Provider; +import com.cloud.network.Network.Service; +import com.cloud.network.NetworkModel; +import com.cloud.network.Networks.TrafficType; +import com.cloud.network.PhysicalNetworkServiceProvider; +import com.cloud.network.VirtualRouterProvider; +import com.cloud.network.VirtualRouterProvider.Type; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.PhysicalNetworkServiceProviderDao; +import com.cloud.network.dao.VirtualRouterProviderDao; +import com.cloud.network.lb.LoadBalancingRule; +import com.cloud.network.lb.LoadBalancingRule.LbDestination; +import com.cloud.network.lb.LoadBalancingRule.LbHealthCheckPolicy; +import com.cloud.network.lb.LoadBalancingRule.LbStickinessPolicy; +import com.cloud.network.lb.LoadBalancingRulesManager; +import com.cloud.network.router.VirtualNetworkApplianceManager; +import com.cloud.network.router.VirtualRouter; +import com.cloud.network.router.VirtualRouter.RedundantState; +import com.cloud.network.router.VirtualRouter.Role; +import com.cloud.network.rules.FirewallRule; +import com.cloud.offering.NetworkOffering; +import com.cloud.offering.ServiceOffering; +import com.cloud.offerings.dao.NetworkOfferingDao; +import com.cloud.resource.ResourceManager; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.storage.Storage; +import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.User; +import com.cloud.user.UserVO; +import com.cloud.user.dao.UserDao; +import com.cloud.utils.Pair; +import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.db.DB; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.Ip; +import com.cloud.utils.net.NetUtils; +import com.cloud.vm.DomainRouterVO; +import com.cloud.vm.Nic; +import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; +import com.cloud.vm.ReservationContext; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.VirtualMachineGuru; +import com.cloud.vm.VirtualMachineManager; +import com.cloud.vm.VirtualMachineName; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.VirtualMachineProfile.Param; +import com.cloud.vm.dao.DomainRouterDao; +import com.cloud.vm.dao.NicDao; + +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.lb.ApplicationLoadBalancerRuleVO; +import org.apache.cloudstack.lb.dao.ApplicationLoadBalancerRuleDao; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class InternalLoadBalancerVMManagerImpl extends ManagerBase implements InternalLoadBalancerVMManager, InternalLoadBalancerVMService, VirtualMachineGuru { + private static final Logger s_logger = LoggerFactory.getLogger(InternalLoadBalancerVMManagerImpl.class); + static final private String InternalLbVmNamePrefix = "b"; + + private String _instance; + private String _mgmtHost; + private String _mgmtCidr; + private long _internalLbVmOfferingId = 0L; + + @Inject + IpAddressManager _ipAddrMgr; + @Inject + VirtualMachineManager _itMgr; + @Inject + DomainRouterDao _internalLbVmDao; + @Inject + ConfigurationDao _configDao; + @Inject + AgentManager _agentMgr; + @Inject + DataCenterDao _dcDao; + @Inject + VirtualRouterProviderDao _vrProviderDao; + @Inject + ApplicationLoadBalancerRuleDao _lbDao; + @Inject + NetworkModel _ntwkModel; + @Inject + LoadBalancingRulesManager _lbMgr; + @Inject + NicDao _nicDao; + @Inject + AccountManager _accountMgr; + @Inject + NetworkDao _networkDao; + @Inject + NetworkOrchestrationService _ntwkMgr; + @Inject + ServiceOfferingDao _serviceOfferingDao; + @Inject + PhysicalNetworkServiceProviderDao _physicalProviderDao; + @Inject + NetworkOfferingDao _networkOfferingDao; + @Inject + VMTemplateDao _templateDao; + @Inject + ResourceManager _resourceMgr; + @Inject + UserDao _userDao; + + @Override + public boolean finalizeVirtualMachineProfile(final VirtualMachineProfile profile, final DeployDestination dest, final ReservationContext context) { + + //Internal LB vm starts up with 2 Nics + //Nic #1 - Guest Nic with IP address that would act as the LB entry point + //Nic #2 - Control/Management Nic + + final StringBuilder buf = profile.getBootArgsBuilder(); + buf.append(" template=domP"); + buf.append(" name=").append(profile.getHostName()); + + if (Boolean.valueOf(_configDao.getValue("system.vm.random.password"))) { + buf.append(" vmpassword=").append(_configDao.getValue("system.vm.password")); + } + + NicProfile controlNic = null; + Network guestNetwork = null; + + for (final NicProfile nic : profile.getNics()) { + final int deviceId = nic.getDeviceId(); + buf.append(" eth").append(deviceId).append("ip=").append(nic.getIPv4Address()); + buf.append(" eth").append(deviceId).append("mask=").append(nic.getIPv4Netmask()); + + if (nic.isDefaultNic()) { + buf.append(" gateway=").append(nic.getIPv4Gateway()); + buf.append(" dns1=").append(nic.getIPv4Gateway()); + } + + if (nic.getTrafficType() == TrafficType.Guest) { + guestNetwork = _ntwkModel.getNetwork(nic.getNetworkId()); + } else if (nic.getTrafficType() == TrafficType.Management) { + buf.append(" localgw=").append(dest.getPod().getGateway()); + } else if (nic.getTrafficType() == TrafficType.Control) { + controlNic = nic; + } + } + + if (controlNic == null) { + throw new CloudRuntimeException("Didn't start a control port"); + } + + if (guestNetwork != null) { + final String domain = guestNetwork.getNetworkDomain(); + if (domain != null) { + buf.append(" domain=" + domain); + } + } + + final String type = "ilbvm"; + buf.append(" type=" + type); + + if (s_logger.isDebugEnabled()) { + s_logger.debug("Boot Args for " + profile + ": " + buf.toString()); + } + + return true; + } + + @Override + public boolean finalizeDeployment(final Commands cmds, final VirtualMachineProfile profile, final DeployDestination dest, final ReservationContext context) + throws ResourceUnavailableException { + + final DomainRouterVO internalLbVm = _internalLbVmDao.findById(profile.getId()); + + final List nics = profile.getNics(); + for (final NicProfile nic : nics) { + if (nic.getTrafficType() == TrafficType.Control) { + internalLbVm.setPrivateIpAddress(nic.getIPv4Address()); + internalLbVm.setPrivateMacAddress(nic.getMacAddress()); + } + } + _internalLbVmDao.update(internalLbVm.getId(), internalLbVm); + + finalizeCommandsOnStart(cmds, profile); + return true; + } + + @Override + public boolean finalizeStart(final VirtualMachineProfile profile, final long hostId, final Commands cmds, final ReservationContext context) { + DomainRouterVO internalLbVm = _internalLbVmDao.findById(profile.getId()); + + boolean result = true; + + Answer answer = cmds.getAnswer("checkSsh"); + if (answer != null && answer instanceof CheckSshAnswer) { + final CheckSshAnswer sshAnswer = (CheckSshAnswer)answer; + if (sshAnswer == null || !sshAnswer.getResult()) { + s_logger.warn("Unable to ssh to the internal LB VM: " + sshAnswer.getDetails()); + result = false; + } + } else { + result = false; + } + if (result == false) { + return result; + } + + //Get guest network info + final List guestNetworks = new ArrayList(); + final List internalLbVmNics = _nicDao.listByVmId(profile.getId()); + for (final Nic internalLbVmNic : internalLbVmNics) { + final Network network = _ntwkModel.getNetwork(internalLbVmNic.getNetworkId()); + if (network.getTrafficType() == TrafficType.Guest) { + guestNetworks.add(network); + } + } + + answer = cmds.getAnswer("getDomRVersion"); + if (answer != null && answer instanceof GetDomRVersionAnswer) { + final GetDomRVersionAnswer versionAnswer = (GetDomRVersionAnswer)answer; + if (answer == null || !answer.getResult()) { + s_logger.warn("Unable to get the template/scripts version of internal LB VM " + internalLbVm.getInstanceName() + " due to: " + versionAnswer.getDetails()); + result = false; + } else { + internalLbVm.setTemplateVersion(versionAnswer.getTemplateVersion()); + internalLbVm.setScriptsVersion(versionAnswer.getScriptsVersion()); + internalLbVm = _internalLbVmDao.persist(internalLbVm, guestNetworks); + } + } else { + result = false; + } + + return result; + } + + @Override + public boolean finalizeCommandsOnStart(final Commands cmds, final VirtualMachineProfile profile) { + final DomainRouterVO internalLbVm = _internalLbVmDao.findById(profile.getId()); + final NicProfile controlNic = getNicProfileByTrafficType(profile, TrafficType.Control); + + if (controlNic == null) { + s_logger.error("Control network doesn't exist for the internal LB vm " + internalLbVm); + return false; + } + + finalizeSshAndVersionOnStart(cmds, profile, internalLbVm, controlNic); + + // restart network if restartNetwork = false is not specified in profile parameters + boolean reprogramGuestNtwk = true; + if (profile.getParameter(Param.ReProgramGuestNetworks) != null && (Boolean)profile.getParameter(Param.ReProgramGuestNetworks) == false) { + reprogramGuestNtwk = false; + } + + final VirtualRouterProvider lbProvider = _vrProviderDao.findById(internalLbVm.getElementId()); + if (lbProvider == null) { + throw new CloudRuntimeException("Cannot find related element " + Type.InternalLbVm + " of vm: " + internalLbVm.getHostName()); + } + + final Provider provider = Network.Provider.getProvider(lbProvider.getType().toString()); + if (provider == null) { + throw new CloudRuntimeException("Cannot find related provider of provider: " + lbProvider.getType().toString()); + } + + if (reprogramGuestNtwk) { + final NicProfile guestNic = getNicProfileByTrafficType(profile, TrafficType.Guest); + finalizeLbRulesForIp(cmds, internalLbVm, provider, new Ip(guestNic.getIPv4Address()), guestNic.getNetworkId()); + } + + return true; + } + + @Override + public void finalizeStop(final VirtualMachineProfile profile, final Answer answer) { + } + + @Override + public void finalizeExpunge(final VirtualMachine vm) { + } + + @Override + public void prepareStop(final VirtualMachineProfile profile) { + } + + @Override + public boolean configure(final String name, final Map params) throws ConfigurationException { + final Map configs = _configDao.getConfiguration("AgentManager", params); + _instance = configs.get("instance.name"); + if (_instance == null) { + _instance = "DEFAULT"; + } + + _mgmtHost = configs.get("host"); + _mgmtCidr = _configDao.getValue(Config.ManagementNetwork.key()); + + final String offUUID = configs.get(Config.InternalLbVmServiceOfferingId.key()); + if (offUUID != null && !offUUID.isEmpty()) { + //get the id by offering UUID + final ServiceOfferingVO off = _serviceOfferingDao.findByUuid(offUUID); + if (off != null) { + _internalLbVmOfferingId = off.getId(); + } else { + s_logger.warn("Invalid offering UUID is passed in " + Config.InternalLbVmServiceOfferingId.key() + "; the default offering will be used instead"); + } + } + + //if offering wasn't set, try to get the default one + if (_internalLbVmOfferingId == 0L) { + final List offerings = _serviceOfferingDao.createSystemServiceOfferings("System Offering For Internal LB VM", + ServiceOffering.internalLbVmDefaultOffUniqueName, 1, InternalLoadBalancerVMManager.DEFAULT_INTERNALLB_VM_RAMSIZE, + InternalLoadBalancerVMManager.DEFAULT_INTERNALLB_VM_CPU_MHZ, null, null, true, null, + Storage.ProvisioningType.THIN, true, null, true, VirtualMachine.Type.InternalLoadBalancerVm, true); + if (offerings == null || offerings.size() < 2) { + final String msg = "Data integrity problem : System Offering For Internal LB VM has been removed?"; + s_logger.error(msg); + throw new ConfigurationException(msg); + } + } + + _itMgr.registerGuru(VirtualMachine.Type.InternalLoadBalancerVm, this); + + if (s_logger.isInfoEnabled()) { + s_logger.info(getName() + " has been configured"); + } + + return true; + } + + @Override + public String getName() { + return _name; + } + + protected NicProfile getNicProfileByTrafficType(final VirtualMachineProfile profile, final TrafficType trafficType) { + for (final NicProfile nic : profile.getNics()) { + if (nic.getTrafficType() == trafficType && nic.getIPv4Address() != null) { + return nic; + } + } + return null; + } + + protected void finalizeSshAndVersionOnStart(final Commands cmds, final VirtualMachineProfile profile, final DomainRouterVO router, final NicProfile controlNic) { + cmds.addCommand("checkSsh", new CheckSshCommand(profile.getInstanceName(), controlNic.getIPv4Address(), 3922)); + + // Update internal lb vm template/scripts version + final GetDomRVersionCmd command = new GetDomRVersionCmd(); + command.setAccessDetail(NetworkElementCommand.ROUTER_IP, controlNic.getIPv4Address()); + command.setAccessDetail(NetworkElementCommand.ROUTER_NAME, router.getInstanceName()); + cmds.addCommand("getDomRVersion", command); + } + + protected void finalizeLbRulesForIp(final Commands cmds, final DomainRouterVO internalLbVm, final Provider provider, final Ip sourceIp, final long guestNtwkId) { + s_logger.debug("Resending load balancing rules as a part of start for " + internalLbVm); + final List lbs = _lbDao.listBySrcIpSrcNtwkId(sourceIp, guestNtwkId); + final List lbRules = new ArrayList(); + if (_ntwkModel.isProviderSupportServiceInNetwork(guestNtwkId, Service.Lb, provider)) { + // Re-apply load balancing rules + for (final ApplicationLoadBalancerRuleVO lb : lbs) { + final List dstList = _lbMgr.getExistingDestinations(lb.getId()); + final List policyList = _lbMgr.getStickinessPolicies(lb.getId()); + final List hcPolicyList = _lbMgr.getHealthCheckPolicies(lb.getId()); + final LoadBalancingRule loadBalancing = new LoadBalancingRule(lb, dstList, policyList, hcPolicyList, sourceIp); + lbRules.add(loadBalancing); + } + } + + s_logger.debug("Found " + lbRules.size() + " load balancing rule(s) to apply as a part of Intenrnal LB vm" + internalLbVm + " start."); + if (!lbRules.isEmpty()) { + createApplyLoadBalancingRulesCommands(lbRules, internalLbVm, cmds, guestNtwkId); + } + } + + private void createApplyLoadBalancingRulesCommands(final List rules, final VirtualRouter internalLbVm, final Commands cmds, final long guestNetworkId) { + + final LoadBalancerTO[] lbs = new LoadBalancerTO[rules.size()]; + int i = 0; + final boolean inline = false; + for (final LoadBalancingRule rule : rules) { + final boolean revoked = rule.getState().equals(FirewallRule.State.Revoke); + final String protocol = rule.getProtocol(); + final String algorithm = rule.getAlgorithm(); + final String uuid = rule.getUuid(); + + final String srcIp = rule.getSourceIp().addr(); + final int srcPort = rule.getSourcePortStart(); + final List destinations = rule.getDestinations(); + final List stickinessPolicies = rule.getStickinessPolicies(); + final LoadBalancerTO lb = new LoadBalancerTO(uuid, srcIp, srcPort, protocol, algorithm, revoked, false, inline, destinations, stickinessPolicies); + lbs[i++] = lb; + } + + final Network guestNetwork = _ntwkModel.getNetwork(guestNetworkId); + final Nic guestNic = _nicDao.findByNtwkIdAndInstanceId(guestNetwork.getId(), internalLbVm.getId()); + final NicProfile guestNicProfile = + new NicProfile(guestNic, guestNetwork, guestNic.getBroadcastUri(), guestNic.getIsolationUri(), _ntwkModel.getNetworkRate(guestNetwork.getId(), + internalLbVm.getId()), _ntwkModel.isSecurityGroupSupportedInNetwork(guestNetwork), _ntwkModel.getNetworkTag(internalLbVm.getHypervisorType(), + guestNetwork)); + + final NetworkOffering offering = _networkOfferingDao.findById(guestNetwork.getNetworkOfferingId()); + String maxconn = null; + if (offering.getConcurrentConnections() == null) { + maxconn = _configDao.getValue(Config.NetworkLBHaproxyMaxConn.key()); + } else { + maxconn = offering.getConcurrentConnections().toString(); + } + final LoadBalancerConfigCommand cmd = + new LoadBalancerConfigCommand(lbs, guestNic.getIPv4Address(), guestNic.getIPv4Address(), internalLbVm.getPrivateIpAddress(), _itMgr.toNicTO(guestNicProfile, + internalLbVm.getHypervisorType()), internalLbVm.getVpcId(), maxconn, offering.isKeepAliveEnabled()); + + cmd.lbStatsVisibility = _configDao.getValue(Config.NetworkLBHaproxyStatsVisbility.key()); + cmd.lbStatsUri = _configDao.getValue(Config.NetworkLBHaproxyStatsUri.key()); + cmd.lbStatsAuth = _configDao.getValue(Config.NetworkLBHaproxyStatsAuth.key()); + cmd.lbStatsPort = _configDao.getValue(Config.NetworkLBHaproxyStatsPort.key()); + + cmd.setAccessDetail(NetworkElementCommand.ROUTER_IP, getInternalLbControlIp(internalLbVm.getId())); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_GUEST_IP, guestNic.getIPv4Address()); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, internalLbVm.getInstanceName()); + final DataCenterVO dcVo = _dcDao.findById(internalLbVm.getDataCenterId()); + cmd.setAccessDetail(NetworkElementCommand.ZONE_NETWORK_TYPE, dcVo.getNetworkType().toString()); + cmds.addCommand(cmd); + } + + protected String getInternalLbControlIp(final long internalLbVmId) { + String controlIpAddress = null; + final List nics = _nicDao.listByVmId(internalLbVmId); + for (final NicVO nic : nics) { + final Network ntwk = _ntwkModel.getNetwork(nic.getNetworkId()); + if (ntwk.getTrafficType() == TrafficType.Control) { + controlIpAddress = nic.getIPv4Address(); + } + } + + if (controlIpAddress == null) { + s_logger.warn("Unable to find Internal LB control ip in its attached NICs!. Internal LB vm: " + internalLbVmId); + final DomainRouterVO internalLbVm = _internalLbVmDao.findById(internalLbVmId); + return internalLbVm.getPrivateIpAddress(); + } + + return controlIpAddress; + } + + @Override + public boolean destroyInternalLbVm(final long vmId, final Account caller, final Long callerUserId) throws ResourceUnavailableException, ConcurrentOperationException { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Attempting to destroy Internal LB vm " + vmId); + } + + final DomainRouterVO internalLbVm = _internalLbVmDao.findById(vmId); + if (internalLbVm == null) { + return true; + } + + _accountMgr.checkAccess(caller, null, true, internalLbVm); + + _itMgr.expunge(internalLbVm.getUuid()); + _internalLbVmDao.remove(internalLbVm.getId()); + return true; + } + + @Override + public VirtualRouter stopInternalLbVm(final long vmId, final boolean forced, final Account caller, final long callerUserId) throws ConcurrentOperationException, ResourceUnavailableException { + final DomainRouterVO internalLbVm = _internalLbVmDao.findById(vmId); + if (internalLbVm == null || internalLbVm.getRole() != Role.INTERNAL_LB_VM) { + throw new InvalidParameterValueException("Can't find internal lb vm by id specified"); + } + + //check permissions + _accountMgr.checkAccess(caller, null, true, internalLbVm); + + return stopInternalLbVm(internalLbVm, forced, caller, callerUserId); + } + + protected VirtualRouter stopInternalLbVm(final DomainRouterVO internalLbVm, final boolean forced, final Account caller, final long callerUserId) throws ResourceUnavailableException, + ConcurrentOperationException { + s_logger.debug("Stopping internal lb vm " + internalLbVm); + try { + _itMgr.advanceStop(internalLbVm.getUuid(), forced); + return _internalLbVmDao.findById(internalLbVm.getId()); + } catch (final OperationTimedoutException e) { + throw new CloudRuntimeException("Unable to stop " + internalLbVm, e); + } + } + + @Override + public List deployInternalLbVm(final Network guestNetwork, final Ip requestedGuestIp, final DeployDestination dest, final Account owner, final Map params) + throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException { + + final List internalLbVms = findOrDeployInternalLbVm(guestNetwork, requestedGuestIp, dest, owner, params); + + return startInternalLbVms(params, internalLbVms); + } + + protected List startInternalLbVms(final Map params, final List internalLbVms) throws StorageUnavailableException, + InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException { + List runningInternalLbVms = null; + + if (internalLbVms != null) { + runningInternalLbVms = new ArrayList(); + } else { + s_logger.debug("Have no internal lb vms to start"); + return null; + } + + for (DomainRouterVO internalLbVm : internalLbVms) { + if (internalLbVm.getState() != VirtualMachine.State.Running) { + internalLbVm = startInternalLbVm(internalLbVm, _accountMgr.getSystemAccount(), User.UID_SYSTEM, params); + } + + if (internalLbVm != null) { + runningInternalLbVms.add(internalLbVm); + } + } + return runningInternalLbVms; + } + + @DB + protected List findOrDeployInternalLbVm(final Network guestNetwork, final Ip requestedGuestIp, final DeployDestination dest, final Account owner, final Map params) + throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException { + + List internalLbVms = new ArrayList(); + final Network lock = _networkDao.acquireInLockTable(guestNetwork.getId(), NetworkOrchestrationService.NetworkLockTimeout.value()); + if (lock == null) { + throw new ConcurrentOperationException("Unable to lock network " + guestNetwork.getId()); + } + + if (s_logger.isDebugEnabled()) { + s_logger.debug("Lock is acquired for network id " + lock.getId() + " as a part of internal lb startup in " + dest); + } + + final long internalLbProviderId = getInternalLbProviderId(guestNetwork); + + try { + assert guestNetwork.getState() == Network.State.Implemented || guestNetwork.getState() == Network.State.Setup || + guestNetwork.getState() == Network.State.Implementing : "Network is not yet fully implemented: " + guestNetwork; + assert guestNetwork.getTrafficType() == TrafficType.Guest; + + //deploy internal lb vm + final Pair> planAndInternalLbVms = getDeploymentPlanAndInternalLbVms(dest, guestNetwork.getId(), requestedGuestIp); + internalLbVms = planAndInternalLbVms.second(); + final DeploymentPlan plan = planAndInternalLbVms.first(); + + if (internalLbVms.size() > 0) { + s_logger.debug("Found " + internalLbVms.size() + " internal lb vms for the requested IP " + requestedGuestIp.addr()); + return internalLbVms; + } + + final LinkedHashMap> networks = createInternalLbVmNetworks(guestNetwork, plan, requestedGuestIp); + long internalLbVmOfferingId = _internalLbVmOfferingId; + if (internalLbVmOfferingId == 0L) { + final ServiceOfferingVO serviceOffering = _serviceOfferingDao.findDefaultSystemOffering(ServiceOffering.internalLbVmDefaultOffUniqueName, ConfigurationManagerImpl.SystemVMUseLocalStorage.valueIn(dest.getDataCenter().getId())); + internalLbVmOfferingId = serviceOffering.getId(); + } + //Pass startVm=false as we are holding the network lock that needs to be released at the end of vm allocation + final DomainRouterVO internalLbVm = + deployInternalLbVm(owner, dest, plan, params, internalLbProviderId, internalLbVmOfferingId, guestNetwork.getVpcId(), networks, false); + if (internalLbVm != null) { + _internalLbVmDao.addRouterToGuestNetwork(internalLbVm, guestNetwork); + internalLbVms.add(internalLbVm); + } + } finally { + if (lock != null) { + _networkDao.releaseFromLockTable(lock.getId()); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Lock is released for network id " + lock.getId() + " as a part of internal lb vm startup in " + dest); + } + } + } + return internalLbVms; + } + + protected long getInternalLbProviderId(final Network guestNetwork) { + final Type type = Type.InternalLbVm; + final long physicalNetworkId = _ntwkModel.getPhysicalNetworkId(guestNetwork); + + final PhysicalNetworkServiceProvider provider = _physicalProviderDao.findByServiceProvider(physicalNetworkId, type.toString()); + if (provider == null) { + throw new CloudRuntimeException("Cannot find service provider " + type.toString() + " in physical network " + physicalNetworkId); + } + + final VirtualRouterProvider internalLbProvider = _vrProviderDao.findByNspIdAndType(provider.getId(), type); + if (internalLbProvider == null) { + throw new CloudRuntimeException("Cannot find provider " + type.toString() + " as service provider " + provider.getId()); + } + + return internalLbProvider.getId(); + } + + protected LinkedHashMap> createInternalLbVmNetworks(final Network guestNetwork, final DeploymentPlan plan, final Ip guestIp) throws ConcurrentOperationException, + InsufficientAddressCapacityException { + + //Form networks + final LinkedHashMap> networks = new LinkedHashMap>(3); + + //1) Guest network - default + if (guestNetwork != null) { + s_logger.debug("Adding nic for Internal LB in Guest network " + guestNetwork); + final NicProfile guestNic = new NicProfile(); + if (guestIp != null) { + guestNic.setIPv4Address(guestIp.addr()); + } else { + guestNic.setIPv4Address(_ipAddrMgr.acquireGuestIpAddress(guestNetwork, null)); + } + guestNic.setIPv4Gateway(guestNetwork.getGateway()); + guestNic.setBroadcastUri(guestNetwork.getBroadcastUri()); + guestNic.setBroadcastType(guestNetwork.getBroadcastDomainType()); + guestNic.setIsolationUri(guestNetwork.getBroadcastUri()); + guestNic.setMode(guestNetwork.getMode()); + final String gatewayCidr = guestNetwork.getCidr(); + guestNic.setIPv4Netmask(NetUtils.getCidrNetmask(gatewayCidr)); + guestNic.setDefaultNic(true); + networks.put(guestNetwork, new ArrayList(Arrays.asList(guestNic))); + } + + //2) Control network + s_logger.debug("Adding nic for Internal LB vm in Control network "); + final List offerings = _ntwkModel.getSystemAccountNetworkOfferings(NetworkOffering.SystemControlNetwork); + final NetworkOffering controlOffering = offerings.get(0); + final Network controlConfig = _ntwkMgr.setupNetwork(_accountMgr.getSystemAccount(), controlOffering, plan, null, null, false).get(0); + networks.put(controlConfig, new ArrayList()); + + return networks; + } + + protected Pair> getDeploymentPlanAndInternalLbVms(final DeployDestination dest, final long guestNetworkId, final Ip requestedGuestIp) { + final long dcId = dest.getDataCenter().getId(); + final DeploymentPlan plan = new DataCenterDeployment(dcId); + final List internalLbVms = findInternalLbVms(guestNetworkId, requestedGuestIp); + + return new Pair>(plan, internalLbVms); + + } + + @Override + public List findInternalLbVms(final long guestNetworkId, final Ip requestedGuestIp) { + final List internalLbVms = _internalLbVmDao.listByNetworkAndRole(guestNetworkId, Role.INTERNAL_LB_VM); + if (requestedGuestIp != null && !internalLbVms.isEmpty()) { + final Iterator it = internalLbVms.iterator(); + while (it.hasNext()) { + final DomainRouterVO vm = it.next(); + final Nic nic = _nicDao.findByNtwkIdAndInstanceId(guestNetworkId, vm.getId()); + if (!nic.getIPv4Address().equalsIgnoreCase(requestedGuestIp.addr())) { + it.remove(); + } + } + } + return internalLbVms; + } + + protected DomainRouterVO deployInternalLbVm(final Account owner, final DeployDestination dest, final DeploymentPlan plan, final Map params, final long internalLbProviderId, + final long svcOffId, final Long vpcId, final LinkedHashMap> networks, final boolean startVm) throws ConcurrentOperationException, + InsufficientAddressCapacityException, InsufficientServerCapacityException, InsufficientCapacityException, StorageUnavailableException, + ResourceUnavailableException { + + final ServiceOfferingVO routerOffering = _serviceOfferingDao.findById(svcOffId); + + // Internal lb is the network element, we don't know the hypervisor type yet. + // Try to allocate the internal lb twice using diff hypervisors, and when failed both times, throw the exception up + final List hypervisors = getHypervisors(dest, plan, null); + + int allocateRetry = 0; + int startRetry = 0; + DomainRouterVO internalLbVm = null; + for (final Iterator iter = hypervisors.iterator(); iter.hasNext();) { + final HypervisorType hType = iter.next(); + try { + final long id = _internalLbVmDao.getNextInSequence(Long.class, "id"); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Creating the internal lb vm " + id + " in datacenter " + dest.getDataCenter() + " with hypervisor type " + hType); + } + String templateName = null; + switch (hType) { + case XenServer: + templateName = VirtualNetworkApplianceManager.RouterTemplateXen.valueIn(dest.getDataCenter().getId()); + break; + case KVM: + templateName = VirtualNetworkApplianceManager.RouterTemplateKvm.valueIn(dest.getDataCenter().getId()); + break; + default: + break; + } + final VMTemplateVO template = _templateDao.findRoutingTemplate(hType, templateName); + + if (template == null) { + s_logger.debug(hType + " won't support system vm, skip it"); + continue; + } + + long userId = CallContext.current().getCallingUserId(); + if (CallContext.current().getCallingAccount().getId() != owner.getId()) { + final List userVOs = _userDao.listByAccount(owner.getAccountId()); + if (!userVOs.isEmpty()) { + userId = userVOs.get(0).getId(); + } + } + + internalLbVm = + new DomainRouterVO(id, routerOffering.getId(), internalLbProviderId, VirtualMachineName.getSystemVmName(id, _instance, InternalLbVmNamePrefix), + template.getId(), template.getHypervisorType(), template.getGuestOSId(), owner.getDomainId(), owner.getId(), userId, false, RedundantState.UNKNOWN, false, false, VirtualMachine.Type.InternalLoadBalancerVm, vpcId); + internalLbVm.setRole(Role.INTERNAL_LB_VM); + internalLbVm = _internalLbVmDao.persist(internalLbVm); + _itMgr.allocate(internalLbVm.getInstanceName(), template, routerOffering, networks, plan, null); + internalLbVm = _internalLbVmDao.findById(internalLbVm.getId()); + } catch (final InsufficientCapacityException ex) { + if (allocateRetry < 2 && iter.hasNext()) { + s_logger.debug("Failed to allocate the Internal lb vm with hypervisor type " + hType + ", retrying one more time"); + continue; + } else { + throw ex; + } + } finally { + allocateRetry++; + } + + if (startVm) { + try { + internalLbVm = startInternalLbVm(internalLbVm, _accountMgr.getSystemAccount(), User.UID_SYSTEM, params); + break; + } catch (final InsufficientCapacityException ex) { + if (startRetry < 2 && iter.hasNext()) { + s_logger.debug("Failed to start the Internal lb vm " + internalLbVm + " with hypervisor type " + hType + ", " + + "destroying it and recreating one more time"); + // destroy the internal lb vm + destroyInternalLbVm(internalLbVm.getId(), _accountMgr.getSystemAccount(), User.UID_SYSTEM); + continue; + } else { + throw ex; + } + } finally { + startRetry++; + } + } else { + //return stopped internal lb vm + return internalLbVm; + } + } + return internalLbVm; + } + + protected DomainRouterVO startInternalLbVm(DomainRouterVO internalLbVm, final Account caller, final long callerUserId, final Map params) + throws StorageUnavailableException, InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException { + s_logger.debug("Starting Internal LB VM " + internalLbVm); + _itMgr.start(internalLbVm.getUuid(), params, null, null); + if (internalLbVm.isStopPending()) { + s_logger.info("Clear the stop pending flag of Internal LB VM " + internalLbVm.getHostName() + " after start router successfully!"); + internalLbVm.setStopPending(false); + internalLbVm = _internalLbVmDao.persist(internalLbVm); + } + return _internalLbVmDao.findById(internalLbVm.getId()); + } + + protected List getHypervisors(final DeployDestination dest, final DeploymentPlan plan, final List supportedHypervisors) + throws InsufficientServerCapacityException { + List hypervisors = new ArrayList(); + + final HypervisorType defaults = _resourceMgr.getDefaultHypervisor(dest.getDataCenter().getId()); + if (defaults != HypervisorType.None) { + hypervisors.add(defaults); + } else { + //if there is no default hypervisor, get it from the cluster + hypervisors = _resourceMgr.getSupportedHypervisorTypes(dest.getDataCenter().getId(), true, plan.getPodId()); + } + + //keep only elements defined in supported hypervisors + final StringBuilder hTypesStr = new StringBuilder(); + if (supportedHypervisors != null && !supportedHypervisors.isEmpty()) { + hypervisors.retainAll(supportedHypervisors); + for (final HypervisorType hType : supportedHypervisors) { + hTypesStr.append(hType).append(" "); + } + } + + if (hypervisors.isEmpty()) { + throw new InsufficientServerCapacityException("Unable to create internal lb vm, " + "there are no clusters in the zone ", DataCenter.class, + dest.getDataCenter().getId()); + } + return hypervisors; + } + + @Override + public boolean applyLoadBalancingRules(final Network network, final List rules, final List internalLbVms) + throws ResourceUnavailableException { + if (rules == null || rules.isEmpty()) { + s_logger.debug("No lb rules to be applied for network " + network); + return true; + } + s_logger.info("lb rules to be applied for network "); + //only one internal lb vm is supported per ip address at this time + if (internalLbVms == null || internalLbVms.isEmpty()) { + throw new CloudRuntimeException("Can't apply the lb rules on network " + network + " as the list of internal lb vms is empty"); + } + + final VirtualRouter lbVm = internalLbVms.get(0); + if (lbVm.getState() == State.Running) { + return sendLBRules(lbVm, rules, network.getId()); + } else if (lbVm.getState() == State.Stopped || lbVm.getState() == State.Stopping) { + s_logger.debug("Internal LB VM " + lbVm.getInstanceName() + " is in " + lbVm.getState() + ", so not sending apply lb rules commands to the backend"); + return true; + } else { + s_logger.warn("Unable to apply lb rules, Internal LB VM is not in the right state " + lbVm.getState()); + throw new ResourceUnavailableException("Unable to apply lb rules; Internal LB VM is not in the right state", DataCenter.class, lbVm.getDataCenterId()); + } + } + + protected boolean sendLBRules(final VirtualRouter internalLbVm, final List rules, final long guestNetworkId) throws ResourceUnavailableException { + final Commands cmds = new Commands(Command.OnError.Continue); + createApplyLoadBalancingRulesCommands(rules, internalLbVm, cmds, guestNetworkId); + return sendCommandsToInternalLbVm(internalLbVm, cmds); + } + + protected boolean sendCommandsToInternalLbVm(final VirtualRouter internalLbVm, final Commands cmds) throws AgentUnavailableException { + Answer[] answers = null; + try { + answers = _agentMgr.send(internalLbVm.getHostId(), cmds); + } catch (final OperationTimedoutException e) { + s_logger.warn("Timed Out", e); + throw new AgentUnavailableException("Unable to send commands to virtual router ", internalLbVm.getHostId(), e); + } + + if (answers == null) { + return false; + } + + if (answers.length != cmds.size()) { + return false; + } + + boolean result = true; + if (answers.length > 0) { + for (final Answer answer : answers) { + if (!answer.getResult()) { + result = false; + break; + } + } + } + return result; + } + + @Override + public VirtualRouter startInternalLbVm(final long internalLbVmId, final Account caller, final long callerUserId) throws StorageUnavailableException, InsufficientCapacityException, + ConcurrentOperationException, ResourceUnavailableException { + + final DomainRouterVO internalLbVm = _internalLbVmDao.findById(internalLbVmId); + if (internalLbVm == null || internalLbVm.getRole() != Role.INTERNAL_LB_VM) { + throw new InvalidParameterValueException("Can't find internal lb vm by id specified"); + } + + //check permissions + _accountMgr.checkAccess(caller, null, true, internalLbVm); + + return startInternalLbVm(internalLbVm, caller, callerUserId, null); + } +} diff --git a/cosmic-core/plugins/network-elements/internal-loadbalancer/src/main/resources/META-INF/cloudstack/core/spring-internallb-core-context.xml b/cosmic-core/plugins/network-elements/internal-loadbalancer/src/main/resources/META-INF/cloudstack/core/spring-internallb-core-context.xml new file mode 100644 index 0000000000..c03887931b --- /dev/null +++ b/cosmic-core/plugins/network-elements/internal-loadbalancer/src/main/resources/META-INF/cloudstack/core/spring-internallb-core-context.xml @@ -0,0 +1,37 @@ + + + + + + + + + + diff --git a/cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbelement/ElementChildTestConfiguration.java b/cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbelement/ElementChildTestConfiguration.java new file mode 100644 index 0000000000..f7823b586b --- /dev/null +++ b/cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbelement/ElementChildTestConfiguration.java @@ -0,0 +1,125 @@ +// 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 org.apache.cloudstack.internallbelement; + +import java.io.IOException; + +import com.cloud.configuration.ConfigurationManager; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.network.IpAddressManager; +import com.cloud.network.NetworkModel; +import com.cloud.network.dao.NetworkServiceMapDao; +import com.cloud.network.dao.PhysicalNetworkServiceProviderDao; +import com.cloud.network.dao.VirtualRouterProviderDao; +import com.cloud.user.AccountManager; +import com.cloud.utils.db.EntityManager; +import com.cloud.utils.net.NetUtils; +import com.cloud.vm.dao.DomainRouterDao; + +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.lb.dao.ApplicationLoadBalancerRuleDao; +import org.apache.cloudstack.network.lb.InternalLoadBalancerVMManager; +import org.apache.cloudstack.test.utils.SpringUtils; +import org.mockito.Mockito; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.filter.TypeFilter; + +@Configuration +@ComponentScan(basePackageClasses = {NetUtils.class}, + includeFilters = {@Filter(value = ElementChildTestConfiguration.Library.class, type = FilterType.CUSTOM)}, + useDefaultFilters = false) +public class ElementChildTestConfiguration { + public static class Library implements TypeFilter { + @Bean + public AccountManager accountManager() { + return Mockito.mock(AccountManager.class); + } + + @Bean + public DomainRouterDao domainRouterDao() { + return Mockito.mock(DomainRouterDao.class); + } + + @Bean + public VirtualRouterProviderDao virtualRouterProviderDao() { + return Mockito.mock(VirtualRouterProviderDao.class); + } + + @Bean + public NetworkModel networkModel() { + return Mockito.mock(NetworkModel.class); + } + + @Bean + public NetworkOrchestrationService networkManager() { + return Mockito.mock(NetworkOrchestrationService.class); + } + + @Bean + public IpAddressManager ipAddressManager() { + return Mockito.mock(IpAddressManager.class); + } + + @Bean + public PhysicalNetworkServiceProviderDao physicalNetworkServiceProviderDao() { + return Mockito.mock(PhysicalNetworkServiceProviderDao.class); + } + + @Bean + public NetworkServiceMapDao networkServiceMapDao() { + return Mockito.mock(NetworkServiceMapDao.class); + } + + @Bean + public InternalLoadBalancerVMManager internalLoadBalancerVMManager() { + return Mockito.mock(InternalLoadBalancerVMManager.class); + } + + @Bean + public ConfigurationManager confugurationManager() { + return Mockito.mock(ConfigurationManager.class); + } + + @Bean + public EntityManager entityManager() { + return Mockito.mock(EntityManager.class); + } + + @Bean + public ApplicationLoadBalancerRuleDao applicationLoadBalancerRuleDao() { + return Mockito.mock(ApplicationLoadBalancerRuleDao.class); + } + + @Bean + public DataCenterDao dataCenterDao() { + return Mockito.mock(DataCenterDao.class); + } + + @Override + public boolean match(MetadataReader mdr, MetadataReaderFactory arg1) throws IOException { + mdr.getClassMetadata().getClassName(); + ComponentScan cs = ElementChildTestConfiguration.class.getAnnotation(ComponentScan.class); + return SpringUtils.includedInBasePackageClasses(mdr.getClassMetadata().getClassName(), cs); + } + } +} diff --git a/cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbelement/InternalLbElementServiceTest.java b/cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbelement/InternalLbElementServiceTest.java new file mode 100644 index 0000000000..644af93268 --- /dev/null +++ b/cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbelement/InternalLbElementServiceTest.java @@ -0,0 +1,185 @@ +// 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 org.apache.cloudstack.internallbelement; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import javax.inject.Inject; + +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.network.VirtualRouterProvider; +import com.cloud.network.VirtualRouterProvider.Type; +import com.cloud.network.dao.PhysicalNetworkServiceProviderDao; +import com.cloud.network.dao.PhysicalNetworkServiceProviderVO; +import com.cloud.network.dao.VirtualRouterProviderDao; +import com.cloud.network.element.VirtualRouterProviderVO; +import com.cloud.user.AccountManager; +import com.cloud.utils.component.ComponentContext; + +import org.apache.cloudstack.network.element.InternalLoadBalancerElementService; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Matchers; +import org.mockito.Mockito; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = "classpath:/lb_element.xml") +public class InternalLbElementServiceTest { + //The interface to test + @Inject + InternalLoadBalancerElementService _lbElSvc; + + //Mocked interfaces + @Inject + AccountManager _accountMgr; + @Inject + VirtualRouterProviderDao _vrProviderDao; + @Inject + PhysicalNetworkServiceProviderDao _pNtwkProviderDao; + + long validElId = 1L; + long nonExistingElId = 2L; + long invalidElId = 3L; //not of VirtualRouterProviderType + + long validProviderId = 1L; + long nonExistingProviderId = 2L; + long invalidProviderId = 3L; + + @Before + public void setUp() { + + ComponentContext.initComponentsLifeCycle(); + VirtualRouterProviderVO validElement = new VirtualRouterProviderVO(1, Type.InternalLbVm); + VirtualRouterProviderVO invalidElement = new VirtualRouterProviderVO(1, Type.VirtualRouter); + + Mockito.when(_vrProviderDao.findById(validElId)).thenReturn(validElement); + Mockito.when(_vrProviderDao.findById(invalidElId)).thenReturn(invalidElement); + + Mockito.when(_vrProviderDao.persist(validElement)).thenReturn(validElement); + + Mockito.when(_vrProviderDao.findByNspIdAndType(validProviderId, Type.InternalLbVm)).thenReturn(validElement); + + PhysicalNetworkServiceProviderVO validProvider = new PhysicalNetworkServiceProviderVO(1, "InternalLoadBalancerElement"); + PhysicalNetworkServiceProviderVO invalidProvider = new PhysicalNetworkServiceProviderVO(1, "Invalid name!"); + + Mockito.when(_pNtwkProviderDao.findById(validProviderId)).thenReturn(validProvider); + Mockito.when(_pNtwkProviderDao.findById(invalidProviderId)).thenReturn(invalidProvider); + + Mockito.when(_vrProviderDao.persist(Matchers.any(VirtualRouterProviderVO.class))).thenReturn(validElement); + } + + //TESTS FOR getInternalLoadBalancerElement METHOD + + @Test(expected = InvalidParameterValueException.class) + public void findNonExistingVm() { + String expectedExcText = null; + try { + _lbElSvc.getInternalLoadBalancerElement(nonExistingElId); + } catch (InvalidParameterValueException e) { + expectedExcText = e.getMessage(); + throw e; + } finally { + assertEquals("Test failed. The non-existing intenral lb provider was found" + expectedExcText, expectedExcText, + "Unable to find InternalLoadBalancerElementService by id"); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void findInvalidVm() { + String expectedExcText = null; + try { + _lbElSvc.getInternalLoadBalancerElement(invalidElId); + } catch (InvalidParameterValueException e) { + expectedExcText = e.getMessage(); + throw e; + } finally { + assertEquals("Test failed. The non-existing intenral lb provider was found" + expectedExcText, expectedExcText, + "Unable to find InternalLoadBalancerElementService by id"); + } + } + + @Test + public void findValidVm() { + VirtualRouterProvider provider = null; + try { + provider = _lbElSvc.getInternalLoadBalancerElement(validElId); + } finally { + assertNotNull("Test failed. Couldn't find the VR provider by the valid id", provider); + } + } + + //TESTS FOR configureInternalLoadBalancerElement METHOD + + @Test(expected = InvalidParameterValueException.class) + public void configureNonExistingVm() { + + _lbElSvc.configureInternalLoadBalancerElement(nonExistingElId, true); + + } + + @Test(expected = InvalidParameterValueException.class) + public void ConfigureInvalidVm() { + _lbElSvc.configureInternalLoadBalancerElement(invalidElId, true); + } + + @Test + public void enableProvider() { + VirtualRouterProvider provider = null; + try { + provider = _lbElSvc.configureInternalLoadBalancerElement(validElId, true); + } finally { + assertNotNull("Test failed. Couldn't find the VR provider by the valid id ", provider); + assertTrue("Test failed. The provider wasn't eanbled ", provider.isEnabled()); + } + } + + @Test + public void disableProvider() { + VirtualRouterProvider provider = null; + try { + provider = _lbElSvc.configureInternalLoadBalancerElement(validElId, false); + } finally { + assertNotNull("Test failed. Couldn't find the VR provider by the valid id ", provider); + assertFalse("Test failed. The provider wasn't disabled ", provider.isEnabled()); + } + } + + //TESTS FOR addInternalLoadBalancerElement METHOD + + @Test(expected = InvalidParameterValueException.class) + public void addToNonExistingProvider() { + + _lbElSvc.addInternalLoadBalancerElement(nonExistingProviderId); + + } + + public void addToInvalidProvider() { + _lbElSvc.addInternalLoadBalancerElement(invalidProviderId); + } + + @Test + public void addToExistingProvider() { + _lbElSvc.addInternalLoadBalancerElement(validProviderId); + } + +} diff --git a/cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbelement/InternalLbElementTest.java b/cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbelement/InternalLbElementTest.java new file mode 100644 index 0000000000..1ef718c7eb --- /dev/null +++ b/cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbelement/InternalLbElementTest.java @@ -0,0 +1,223 @@ +// 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 org.apache.cloudstack.internallbelement; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +import javax.inject.Inject; + +import com.cloud.agent.api.to.LoadBalancerTO; +import com.cloud.configuration.ConfigurationManager; +import com.cloud.dc.DataCenter; +import com.cloud.dc.DataCenter.NetworkType; +import com.cloud.dc.DataCenterVO; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Network.Provider; +import com.cloud.network.Network.Service; +import com.cloud.network.VirtualRouterProvider.Type; +import com.cloud.network.addr.PublicIp; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.dao.PhysicalNetworkServiceProviderDao; +import com.cloud.network.dao.PhysicalNetworkServiceProviderVO; +import com.cloud.network.dao.VirtualRouterProviderDao; +import com.cloud.network.element.VirtualRouterProviderVO; +import com.cloud.network.lb.LoadBalancingRule; +import com.cloud.network.rules.FirewallRule; +import com.cloud.network.rules.LoadBalancerContainer.Scheme; +import com.cloud.user.AccountManager; +import com.cloud.utils.component.ComponentContext; +import com.cloud.utils.db.EntityManager; +import com.cloud.utils.net.Ip; + +import org.apache.cloudstack.lb.ApplicationLoadBalancerRuleVO; +import org.apache.cloudstack.network.element.InternalLoadBalancerElement; +import org.apache.cloudstack.network.lb.InternalLoadBalancerVMManager; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Matchers; +import org.mockito.Mockito; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = "classpath:/lb_element.xml") +public class InternalLbElementTest { + //The class to test + @Inject + InternalLoadBalancerElement _lbEl; + + //Mocked interfaces + @Inject + AccountManager _accountMgr; + @Inject + VirtualRouterProviderDao _vrProviderDao; + @Inject + PhysicalNetworkServiceProviderDao _pNtwkProviderDao; + @Inject + InternalLoadBalancerVMManager _internalLbMgr; + @Inject + ConfigurationManager _configMgr; + @Inject + EntityManager _entityMgr; + + long validElId = 1L; + long nonExistingElId = 2L; + long invalidElId = 3L; //not of VirtualRouterProviderType + long notEnabledElId = 4L; + + long validProviderId = 1L; + long nonExistingProviderId = 2L; + long invalidProviderId = 3L; + + @Before + public void setUp() { + + ComponentContext.initComponentsLifeCycle(); + VirtualRouterProviderVO validElement = new VirtualRouterProviderVO(1, Type.InternalLbVm); + validElement.setEnabled(true); + VirtualRouterProviderVO invalidElement = new VirtualRouterProviderVO(1, Type.VirtualRouter); + VirtualRouterProviderVO notEnabledElement = new VirtualRouterProviderVO(1, Type.InternalLbVm); + + Mockito.when(_vrProviderDao.findByNspIdAndType(validElId, Type.InternalLbVm)).thenReturn(validElement); + Mockito.when(_vrProviderDao.findByNspIdAndType(invalidElId, Type.InternalLbVm)).thenReturn(invalidElement); + Mockito.when(_vrProviderDao.findByNspIdAndType(notEnabledElId, Type.InternalLbVm)).thenReturn(notEnabledElement); + + Mockito.when(_vrProviderDao.persist(validElement)).thenReturn(validElement); + + Mockito.when(_vrProviderDao.findByNspIdAndType(validProviderId, Type.InternalLbVm)).thenReturn(validElement); + + PhysicalNetworkServiceProviderVO validProvider = new PhysicalNetworkServiceProviderVO(1, "InternalLoadBalancerElement"); + PhysicalNetworkServiceProviderVO invalidProvider = new PhysicalNetworkServiceProviderVO(1, "Invalid name!"); + + Mockito.when(_pNtwkProviderDao.findById(validProviderId)).thenReturn(validProvider); + Mockito.when(_pNtwkProviderDao.findById(invalidProviderId)).thenReturn(invalidProvider); + + Mockito.when(_vrProviderDao.persist(Matchers.any(VirtualRouterProviderVO.class))).thenReturn(validElement); + + DataCenterVO dc = new DataCenterVO(1L, null, null, null, null, null, null, null, null, null, NetworkType.Advanced, null, null); + Mockito.when(_entityMgr.findById(Matchers.eq(DataCenter.class), Matchers.anyLong())).thenReturn(dc); + } + + //TEST FOR getProvider() method + + @Test + public void verifyProviderName() { + Provider pr = _lbEl.getProvider(); + assertEquals("Wrong provider is returned", pr.getName(), Provider.InternalLbVm.getName()); + } + + //TEST FOR isReady() METHOD + + @Test + public void verifyValidProviderState() { + PhysicalNetworkServiceProviderVO provider = new PhysicalNetworkServiceProviderVO(); + provider = setId(provider, validElId); + boolean isReady = _lbEl.isReady(provider); + assertTrue("Valid provider is returned as not ready", isReady); + } + + @Test + public void verifyNonExistingProviderState() { + PhysicalNetworkServiceProviderVO provider = new PhysicalNetworkServiceProviderVO(); + provider = setId(provider, nonExistingElId); + boolean isReady = _lbEl.isReady(provider); + assertFalse("Non existing provider is returned as ready", isReady); + } + + @Test + public void verifyInvalidProviderState() { + PhysicalNetworkServiceProviderVO provider = new PhysicalNetworkServiceProviderVO(); + provider = setId(provider, invalidElId); + boolean isReady = _lbEl.isReady(provider); + assertFalse("Not valid provider is returned as ready", isReady); + } + + @Test + public void verifyNotEnabledProviderState() { + PhysicalNetworkServiceProviderVO provider = new PhysicalNetworkServiceProviderVO(); + provider = setId(provider, notEnabledElId); + boolean isReady = _lbEl.isReady(provider); + assertFalse("Not enabled provider is returned as ready", isReady); + } + + //TEST FOR canEnableIndividualServices METHOD + @Test + public void verifyCanEnableIndividualSvc() { + boolean result = _lbEl.canEnableIndividualServices(); + assertTrue("Wrong value is returned by canEnableIndividualSvc", result); + } + + //TEST FOR verifyServicesCombination METHOD + @Test + public void verifyServicesCombination() { + boolean result = _lbEl.verifyServicesCombination(new HashSet()); + assertTrue("Wrong value is returned by verifyServicesCombination", result); + } + + //TEST FOR applyIps METHOD + @Test + public void verifyApplyIps() throws ResourceUnavailableException { + List ips = new ArrayList(); + boolean result = _lbEl.applyIps(new NetworkVO(), ips, new HashSet()); + assertTrue("Wrong value is returned by applyIps method", result); + } + + //TEST FOR updateHealthChecks METHOD + @Test + public void verifyUpdateHealthChecks() throws ResourceUnavailableException { + List check = _lbEl.updateHealthChecks(new NetworkVO(), new ArrayList()); + assertNull("Wrong value is returned by updateHealthChecks method", check); + } + + //TEST FOR validateLBRule METHOD + @Test + public void verifyValidateLBRule() throws ResourceUnavailableException { + ApplicationLoadBalancerRuleVO lb = new ApplicationLoadBalancerRuleVO(null, null, 22, 22, "roundrobin", 1L, 1L, 1L, new Ip("10.10.10.1"), 1L, Scheme.Internal); + lb.setState(FirewallRule.State.Add); + + LoadBalancingRule rule = new LoadBalancingRule(lb, null, null, null, new Ip("10.10.10.1")); + + boolean result = _lbEl.validateLBRule(new NetworkVO(), rule); + assertTrue("Wrong value is returned by validateLBRule method", result); + } + + private static PhysicalNetworkServiceProviderVO setId(PhysicalNetworkServiceProviderVO vo, long id) { + PhysicalNetworkServiceProviderVO voToReturn = vo; + Class c = voToReturn.getClass(); + try { + Field f = c.getDeclaredField("id"); + f.setAccessible(true); + f.setLong(voToReturn, id); + } catch (NoSuchFieldException ex) { + return null; + } catch (IllegalAccessException ex) { + return null; + } + + return voToReturn; + } + +} diff --git a/cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbvmmgr/InternalLBVMManagerTest.java b/cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbvmmgr/InternalLBVMManagerTest.java new file mode 100644 index 0000000000..af497aefba --- /dev/null +++ b/cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbvmmgr/InternalLBVMManagerTest.java @@ -0,0 +1,387 @@ +// 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 org.apache.cloudstack.internallbvmmgr; + +import java.lang.reflect.Field; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.manager.Commands; +import com.cloud.dc.DataCenter.NetworkType; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.network.NetworkModel; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.lb.LoadBalancingRule; +import com.cloud.network.router.VirtualRouter; +import com.cloud.network.router.VirtualRouter.Role; +import com.cloud.network.rules.FirewallRule; +import com.cloud.network.rules.LoadBalancerContainer.Scheme; +import com.cloud.offerings.NetworkOfferingVO; +import com.cloud.offerings.dao.NetworkOfferingDao; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.storage.Storage; +import com.cloud.storage.Storage.ProvisioningType; +import com.cloud.user.AccountManager; +import com.cloud.user.AccountVO; +import com.cloud.utils.component.ComponentContext; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.Ip; +import com.cloud.vm.DomainRouterVO; +import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.VirtualMachineManager; +import com.cloud.vm.dao.DomainRouterDao; +import com.cloud.vm.dao.NicDao; + +import org.apache.cloudstack.lb.ApplicationLoadBalancerRuleVO; +import org.apache.cloudstack.network.lb.InternalLoadBalancerVMManager; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Matchers; +import org.mockito.Mockito; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import junit.framework.TestCase; + +/** + * Set of unittests for InternalLoadBalancerVMManager + * + */ + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = "classpath:/lb_mgr.xml") +public class InternalLBVMManagerTest extends TestCase { + //The interface to test + @Inject + InternalLoadBalancerVMManager _lbVmMgr; + + //Mocked interfaces + @Inject + AccountManager _accountMgr; + @Inject + ServiceOfferingDao _svcOffDao; + @Inject + DomainRouterDao _domainRouterDao; + @Inject + NicDao _nicDao; + @Inject + AgentManager _agentMgr; + @Inject + NetworkModel _ntwkModel; + @Inject + VirtualMachineManager _itMgr; + @Inject + DataCenterDao _dcDao; + @Inject + NetworkOfferingDao _offeringDao; + long validNtwkId = 1L; + long invalidNtwkId = 2L; + String requestedIp = "10.1.1.1"; + DomainRouterVO vm = null; + NetworkVO ntwk = createNetwork(); + long validVmId = 1L; + long invalidVmId = 2L; + + @Override + @Before + public void setUp() { + //mock system offering creation as it's used by configure() method called by initComponentsLifeCycle + Mockito.when(_accountMgr.getAccount(1L)).thenReturn(new AccountVO()); + ServiceOfferingVO off = new ServiceOfferingVO("alena", 1, 1, + 1, 1, 1, false, "alena", Storage.ProvisioningType.THIN, false, false, null, false, VirtualMachine.Type.InternalLoadBalancerVm, false); + off = setId(off, 1); + List list = new ArrayList(); + list.add(off); + list.add(off); + Mockito.when(_svcOffDao.createSystemServiceOfferings(Matchers.anyString(), Matchers.anyString(), Matchers.anyInt(), Matchers.anyInt(), Matchers.anyInt(), + Matchers.anyInt(), Matchers.anyInt(), Matchers.anyBoolean(), Matchers.anyString(), Matchers.any(ProvisioningType.class), Matchers.anyBoolean(), + Matchers.anyString(), Matchers.anyBoolean(), Matchers.any(VirtualMachine.Type.class), Matchers.anyBoolean())).thenReturn(list); + + ComponentContext.initComponentsLifeCycle(); + + vm = + new DomainRouterVO(1L, off.getId(), 1, "alena", 1, HypervisorType.XenServer, 1, 1, 1, 1, false, null, false, false, + VirtualMachine.Type.InternalLoadBalancerVm, null); + vm.setRole(Role.INTERNAL_LB_VM); + vm = setId(vm, 1); + vm.setPrivateIpAddress("10.2.2.2"); + final NicVO nic = new NicVO("somereserver", 1L, 1L, VirtualMachine.Type.InternalLoadBalancerVm); + nic.setIPv4Address(requestedIp); + + final List emptyList = new ArrayList(); + final List nonEmptyList = new ArrayList(); + nonEmptyList.add(vm); + + Mockito.when(_domainRouterDao.listByNetworkAndRole(invalidNtwkId, Role.INTERNAL_LB_VM)).thenReturn(emptyList); + Mockito.when(_domainRouterDao.listByNetworkAndRole(validNtwkId, Role.INTERNAL_LB_VM)).thenReturn(nonEmptyList); + + Mockito.when(_nicDao.findByNtwkIdAndInstanceId(validNtwkId, 1)).thenReturn(nic); + Mockito.when(_nicDao.findByNtwkIdAndInstanceId(invalidNtwkId, 1)).thenReturn(nic); + + final Answer answer = new Answer(null, true, null); + final Answer[] answers = new Answer[1]; + answers[0] = answer; + + try { + Mockito.when(_agentMgr.send(Matchers.anyLong(), Matchers.any(Commands.class))).thenReturn(answers); + } catch (final AgentUnavailableException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (final OperationTimedoutException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + createNetwork(); + Mockito.when(_ntwkModel.getNetwork(Matchers.anyLong())).thenReturn(ntwk); + + Mockito.when(_itMgr.toNicTO(Matchers.any(NicProfile.class), Matchers.any(HypervisorType.class))).thenReturn(null); + Mockito.when(_domainRouterDao.findById(Matchers.anyLong())).thenReturn(vm); + final DataCenterVO dc = new DataCenterVO(1L, null, null, null, null, null, null, null, null, null, NetworkType.Advanced, null, null); + Mockito.when(_dcDao.findById(Matchers.anyLong())).thenReturn(dc); + final NetworkOfferingVO networkOfferingVO = new NetworkOfferingVO(); + networkOfferingVO.setConcurrentConnections(500); + Mockito.when(_offeringDao.findById(Matchers.anyLong())).thenReturn(networkOfferingVO); + + Mockito.when(_domainRouterDao.findById(validVmId)).thenReturn(vm); + Mockito.when(_domainRouterDao.findById(invalidVmId)).thenReturn(null); + + } + + protected NetworkVO createNetwork() { + ntwk = new NetworkVO(); + try { + ntwk.setBroadcastUri(new URI("somevlan")); + } catch (final URISyntaxException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + ntwk = setId(ntwk, 1L); + return ntwk; + } + + //TESTS FOR findInternalLbVms METHOD + + @Test + public void findInternalLbVmsForInvalidNetwork() { + final List vms = _lbVmMgr.findInternalLbVms(invalidNtwkId, new Ip(requestedIp)); + assertTrue("Non empty vm list was returned for invalid network id", vms.isEmpty()); + } + + @Test + public void findInternalLbVmsForValidNetwork() { + final List vms = _lbVmMgr.findInternalLbVms(validNtwkId, new Ip(requestedIp)); + assertTrue("Empty vm list was returned for valid network id", !vms.isEmpty()); + } + + //TESTS FOR applyLoadBalancingRules METHOD + @Test + public void applyEmptyRulesSet() { + boolean result = false; + final List vms = new ArrayList(); + try { + result = _lbVmMgr.applyLoadBalancingRules(new NetworkVO(), new ArrayList(), vms); + } catch (final ResourceUnavailableException e) { + + } finally { + assertTrue("Got failure when tried to apply empty list of rules", result); + } + } + + @Test(expected = CloudRuntimeException.class) + public void applyWithEmptyVmsSet() { + boolean result = false; + final List vms = new ArrayList(); + final List rules = new ArrayList(); + final LoadBalancingRule rule = new LoadBalancingRule(null, null, null, null, null, null, null); + + rules.add(rule); + try { + result = _lbVmMgr.applyLoadBalancingRules(new NetworkVO(), rules, vms); + } catch (final ResourceUnavailableException e) { + } finally { + assertFalse("Got success when tried to apply with the empty internal lb vm list", result); + } + } + + @Test(expected = ResourceUnavailableException.class) + public void applyToVmInStartingState() throws ResourceUnavailableException { + boolean result = false; + final List vms = new ArrayList(); + vm.setState(State.Starting); + vms.add(vm); + + final List rules = new ArrayList(); + final LoadBalancingRule rule = new LoadBalancingRule(null, null, null, null, null, null, null); + + rules.add(rule); + try { + result = _lbVmMgr.applyLoadBalancingRules(new NetworkVO(), rules, vms); + } finally { + assertFalse("Rules were applied to vm in Starting state", result); + } + } + + @Test + public void applyToVmInStoppedState() throws ResourceUnavailableException { + boolean result = false; + final List vms = new ArrayList(); + vm.setState(State.Stopped); + vms.add(vm); + + final List rules = new ArrayList(); + final LoadBalancingRule rule = new LoadBalancingRule(null, null, null, null, null, null, null); + + rules.add(rule); + try { + result = _lbVmMgr.applyLoadBalancingRules(new NetworkVO(), rules, vms); + } finally { + assertTrue("Rules failed to apply to vm in Stopped state", result); + } + } + + @Test + public void applyToVmInStoppingState() throws ResourceUnavailableException { + boolean result = false; + final List vms = new ArrayList(); + vm.setState(State.Stopping); + vms.add(vm); + + final List rules = new ArrayList(); + final LoadBalancingRule rule = new LoadBalancingRule(null, null, null, null, null, null, null); + + rules.add(rule); + try { + result = _lbVmMgr.applyLoadBalancingRules(new NetworkVO(), rules, vms); + } finally { + assertTrue("Rules failed to apply to vm in Stopping state", result); + } + } + + @Test + public void applyToVmInRunningState() throws ResourceUnavailableException { + boolean result = false; + final List vms = new ArrayList(); + vm.setState(State.Running); + vms.add(vm); + + final List rules = new ArrayList(); + final ApplicationLoadBalancerRuleVO lb = new ApplicationLoadBalancerRuleVO(null, null, 22, 22, "roundrobin", 1L, 1L, 1L, new Ip(requestedIp), 1L, Scheme.Internal); + lb.setState(FirewallRule.State.Add); + + final LoadBalancingRule rule = new LoadBalancingRule(lb, null, null, null, new Ip(requestedIp)); + + rules.add(rule); + + ntwk.getId(); + + try { + result = _lbVmMgr.applyLoadBalancingRules(ntwk, rules, vms); + } finally { + assertTrue("Rules failed to apply to vm in Running state", result); + } + } + + //TESTS FOR destroyInternalLbVm METHOD + @Test + public void destroyNonExistingVM() throws ResourceUnavailableException, ConcurrentOperationException { + boolean result = false; + + try { + result = _lbVmMgr.destroyInternalLbVm(invalidVmId, new AccountVO(), 1L); + } finally { + assertTrue("Failed to destroy non-existing vm", result); + } + } + + @Test + public void destroyExistingVM() throws ResourceUnavailableException, ConcurrentOperationException { + boolean result = false; + + try { + result = _lbVmMgr.destroyInternalLbVm(validVmId, new AccountVO(), 1L); + } finally { + assertTrue("Failed to destroy valid vm", result); + } + } + + private static ServiceOfferingVO setId(final ServiceOfferingVO vo, final long id) { + final ServiceOfferingVO voToReturn = vo; + final Class c = voToReturn.getClass(); + try { + final Field f = c.getSuperclass().getDeclaredField("id"); + f.setAccessible(true); + f.setLong(voToReturn, id); + } catch (final NoSuchFieldException ex) { + return null; + } catch (final IllegalAccessException ex) { + return null; + } + + return voToReturn; + } + + private static NetworkVO setId(final NetworkVO vo, final long id) { + final NetworkVO voToReturn = vo; + final Class c = voToReturn.getClass(); + try { + final Field f = c.getDeclaredField("id"); + f.setAccessible(true); + f.setLong(voToReturn, id); + } catch (final NoSuchFieldException ex) { + return null; + } catch (final IllegalAccessException ex) { + return null; + } + + return voToReturn; + } + + private static DomainRouterVO setId(final DomainRouterVO vo, final long id) { + final DomainRouterVO voToReturn = vo; + final Class c = voToReturn.getClass(); + try { + final Field f = c.getSuperclass().getDeclaredField("id"); + f.setAccessible(true); + f.setLong(voToReturn, id); + } catch (final NoSuchFieldException ex) { + return null; + } catch (final IllegalAccessException ex) { + return null; + } + + return voToReturn; + } + +} diff --git a/cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbvmmgr/InternalLBVMServiceTest.java b/cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbvmmgr/InternalLBVMServiceTest.java new file mode 100644 index 0000000000..0d127cf6f7 --- /dev/null +++ b/cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbvmmgr/InternalLBVMServiceTest.java @@ -0,0 +1,263 @@ +// 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 org.apache.cloudstack.internallbvmmgr; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.exception.StorageUnavailableException; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.network.router.VirtualRouter; +import com.cloud.network.router.VirtualRouter.Role; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.storage.Storage; +import com.cloud.storage.Storage.ProvisioningType; +import com.cloud.user.AccountManager; +import com.cloud.user.AccountVO; +import com.cloud.user.UserVO; +import com.cloud.user.dao.AccountDao; +import com.cloud.utils.component.ComponentContext; +import com.cloud.vm.DomainRouterVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineManager; +import com.cloud.vm.dao.DomainRouterDao; + +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.network.lb.InternalLoadBalancerVMService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Matchers; +import org.mockito.Mockito; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import junit.framework.TestCase; + +/** + * Set of unittests for InternalLoadBalancerVMService + * + */ + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = "classpath:/lb_svc.xml") +public class InternalLBVMServiceTest extends TestCase { + //The interface to test + @Inject + InternalLoadBalancerVMService _lbVmSvc; + + //Mocked interfaces + @Inject + AccountManager _accountMgr; + @Inject + ServiceOfferingDao _svcOffDao; + @Inject + DomainRouterDao _domainRouterDao; + @Inject + VirtualMachineManager _itMgr; + @Inject + AccountDao _accountDao; + + long validVmId = 1L; + long nonExistingVmId = 2L; + long nonInternalLbVmId = 3L; + + @Override + @Before + public void setUp() { + //mock system offering creation as it's used by configure() method called by initComponentsLifeCycle + Mockito.when(_accountMgr.getAccount(1L)).thenReturn(new AccountVO()); + ServiceOfferingVO off = new ServiceOfferingVO("alena", 1, 1, + 1, 1, 1, false, "alena", Storage.ProvisioningType.THIN, false, false, null, false, VirtualMachine.Type.InternalLoadBalancerVm, false); + off = setId(off, 1); + List list = new ArrayList(); + list.add(off); + list.add(off); + Mockito.when(_svcOffDao.createSystemServiceOfferings(Matchers.anyString(), Matchers.anyString(), Matchers.anyInt(), Matchers.anyInt(), Matchers.anyInt(), + Matchers.anyInt(), Matchers.anyInt(), Matchers.anyBoolean(), Matchers.anyString(), Matchers.any(ProvisioningType.class), Matchers.anyBoolean(), + Matchers.anyString(), Matchers.anyBoolean(), Matchers.any(VirtualMachine.Type.class), Matchers.anyBoolean())).thenReturn(list); + + ComponentContext.initComponentsLifeCycle(); + + Mockito.when(_accountMgr.getSystemUser()).thenReturn(new UserVO(1)); + Mockito.when(_accountMgr.getSystemAccount()).thenReturn(new AccountVO(2)); + Mockito.when(_accountDao.findByIdIncludingRemoved(Matchers.anyLong())).thenReturn(new AccountVO(2)); + CallContext.register(_accountMgr.getSystemUser(), _accountMgr.getSystemAccount()); + + final DomainRouterVO validVm = + new DomainRouterVO(validVmId, off.getId(), 1, "alena", 1, HypervisorType.XenServer, 1, 1, 1, 1, false, null, false, false, + VirtualMachine.Type.InternalLoadBalancerVm, null); + validVm.setRole(Role.INTERNAL_LB_VM); + final DomainRouterVO nonInternalLbVm = + new DomainRouterVO(validVmId, off.getId(), 1, "alena", 1, HypervisorType.XenServer, 1, 1, 1, 1, false, null, false, false, + VirtualMachine.Type.DomainRouter, null); + nonInternalLbVm.setRole(Role.VIRTUAL_ROUTER); + + Mockito.when(_domainRouterDao.findById(validVmId)).thenReturn(validVm); + Mockito.when(_domainRouterDao.findById(nonExistingVmId)).thenReturn(null); + Mockito.when(_domainRouterDao.findById(nonInternalLbVmId)).thenReturn(nonInternalLbVm); + } + + @Override + @After + public void tearDown() { + CallContext.unregister(); + } + + //TESTS FOR START COMMAND + + @Test(expected = InvalidParameterValueException.class) + public void startNonExistingVm() { + final String expectedExcText = null; + try { + _lbVmSvc.startInternalLbVm(nonExistingVmId, _accountMgr.getAccount(1L), 1L); + } catch (final StorageUnavailableException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (final InsufficientCapacityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (final ConcurrentOperationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (final ResourceUnavailableException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void startNonInternalLbVmVm() { + final String expectedExcText = null; + try { + _lbVmSvc.startInternalLbVm(nonInternalLbVmId, _accountMgr.getAccount(1L), 1L); + } catch (final StorageUnavailableException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (final InsufficientCapacityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (final ConcurrentOperationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (final ResourceUnavailableException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + @Test + public void startValidLbVmVm() { + VirtualRouter vr = null; + try { + vr = _lbVmSvc.startInternalLbVm(validVmId, _accountMgr.getAccount(1L), 1L); + } catch (final StorageUnavailableException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (final InsufficientCapacityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (final ConcurrentOperationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (final ResourceUnavailableException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } finally { + assertNotNull("Internal LB vm is null which means it failed to start " + vr, vr); + } + } + + //TEST FOR STOP COMMAND + @Test(expected = InvalidParameterValueException.class) + public void stopNonExistingVm() { + final String expectedExcText = null; + try { + _lbVmSvc.stopInternalLbVm(nonExistingVmId, false, _accountMgr.getAccount(1L), 1L); + } catch (final StorageUnavailableException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (final ConcurrentOperationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (final ResourceUnavailableException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void stopNonInternalLbVmVm() { + final String expectedExcText = null; + try { + _lbVmSvc.stopInternalLbVm(nonInternalLbVmId, false, _accountMgr.getAccount(1L), 1L); + } catch (final StorageUnavailableException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (final ConcurrentOperationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (final ResourceUnavailableException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + @Test + public void stopValidLbVmVm() { + VirtualRouter vr = null; + try { + vr = _lbVmSvc.stopInternalLbVm(validVmId, false, _accountMgr.getAccount(1L), 1L); + } catch (final StorageUnavailableException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (final ConcurrentOperationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (final ResourceUnavailableException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } finally { + assertNotNull("Internal LB vm is null which means it failed to stop " + vr, vr); + } + } + + private static ServiceOfferingVO setId(final ServiceOfferingVO vo, final long id) { + final ServiceOfferingVO voToReturn = vo; + final Class c = voToReturn.getClass(); + try { + final Field f = c.getSuperclass().getDeclaredField("id"); + f.setAccessible(true); + f.setLong(voToReturn, id); + } catch (final NoSuchFieldException ex) { + return null; + } catch (final IllegalAccessException ex) { + return null; + } + + return voToReturn; + } +} diff --git a/cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbvmmgr/LbChildTestConfiguration.java b/cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbvmmgr/LbChildTestConfiguration.java new file mode 100644 index 0000000000..33eb27830d --- /dev/null +++ b/cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/java/org/apache/cloudstack/internallbvmmgr/LbChildTestConfiguration.java @@ -0,0 +1,176 @@ +// 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 org.apache.cloudstack.internallbvmmgr; + +import java.io.IOException; + +import com.cloud.agent.AgentManager; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.network.IpAddressManager; +import com.cloud.network.NetworkModel; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.PhysicalNetworkServiceProviderDao; +import com.cloud.network.dao.VirtualRouterProviderDao; +import com.cloud.network.lb.LoadBalancingRulesManager; +import com.cloud.offerings.dao.NetworkOfferingDao; +import com.cloud.resource.ResourceManager; +import com.cloud.server.ConfigurationServer; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.user.AccountManager; +import com.cloud.user.dao.AccountDao; +import com.cloud.utils.net.NetUtils; +import com.cloud.vm.VirtualMachineManager; +import com.cloud.vm.dao.DomainRouterDao; +import com.cloud.vm.dao.NicDao; + +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.lb.dao.ApplicationLoadBalancerRuleDao; +import org.apache.cloudstack.test.utils.SpringUtils; +import org.mockito.Mockito; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.filter.TypeFilter; + +@Configuration +@ComponentScan(basePackageClasses = {NetUtils.class}, + includeFilters = {@Filter(value = LbChildTestConfiguration.Library.class, type = FilterType.CUSTOM)}, + useDefaultFilters = false) +public class LbChildTestConfiguration { + + public static class Library implements TypeFilter { + + @Bean + public AccountManager accountManager() { + return Mockito.mock(AccountManager.class); + } + + @Bean + public VirtualMachineManager virtualMachineManager() { + return Mockito.mock(VirtualMachineManager.class); + } + + @Bean + public DomainRouterDao domainRouterDao() { + return Mockito.mock(DomainRouterDao.class); + } + + @Bean + public ConfigurationDao configurationDao() { + return Mockito.mock(ConfigurationDao.class); + } + + @Bean + public VirtualRouterProviderDao virtualRouterProviderDao() { + return Mockito.mock(VirtualRouterProviderDao.class); + } + + @Bean + public ApplicationLoadBalancerRuleDao applicationLoadBalancerRuleDao() { + return Mockito.mock(ApplicationLoadBalancerRuleDao.class); + } + + @Bean + public NetworkModel networkModel() { + return Mockito.mock(NetworkModel.class); + } + + @Bean + public LoadBalancingRulesManager loadBalancingRulesManager() { + return Mockito.mock(LoadBalancingRulesManager.class); + } + + @Bean + public NicDao nicDao() { + return Mockito.mock(NicDao.class); + } + + @Bean + public NetworkDao networkDao() { + return Mockito.mock(NetworkDao.class); + } + + @Bean + public NetworkOrchestrationService networkManager() { + return Mockito.mock(NetworkOrchestrationService.class); + } + + @Bean + public IpAddressManager ipAddressManager() { + return Mockito.mock(IpAddressManager.class); + } + + @Bean + public ServiceOfferingDao serviceOfferingDao() { + return Mockito.mock(ServiceOfferingDao.class); + } + + @Bean + public PhysicalNetworkServiceProviderDao physicalNetworkServiceProviderDao() { + return Mockito.mock(PhysicalNetworkServiceProviderDao.class); + } + + @Bean + public NetworkOfferingDao networkOfferingDao() { + return Mockito.mock(NetworkOfferingDao.class); + } + + @Bean + public VMTemplateDao vmTemplateDao() { + return Mockito.mock(VMTemplateDao.class); + } + + @Bean + public ResourceManager resourceManager() { + return Mockito.mock(ResourceManager.class); + } + + @Bean + public AgentManager agentManager() { + return Mockito.mock(AgentManager.class); + } + + @Bean + public DataCenterDao dataCenterDao() { + return Mockito.mock(DataCenterDao.class); + } + + @Bean + public ConfigurationServer configurationServer() { + return Mockito.mock(ConfigurationServer.class); + } + + @Bean + public AccountDao accountDao() { + return Mockito.mock(AccountDao.class); + } + + @Override + public boolean match(MetadataReader mdr, MetadataReaderFactory arg1) throws IOException { + mdr.getClassMetadata().getClassName(); + ComponentScan cs = LbChildTestConfiguration.class.getAnnotation(ComponentScan.class); + return SpringUtils.includedInBasePackageClasses(mdr.getClassMetadata().getClassName(), cs); + } + + } +} diff --git a/cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/resources/lb_element.xml b/cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/resources/lb_element.xml new file mode 100644 index 0000000000..5dec9c314f --- /dev/null +++ b/cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/resources/lb_element.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/resources/lb_mgr.xml b/cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/resources/lb_mgr.xml new file mode 100644 index 0000000000..4f202b3b31 --- /dev/null +++ b/cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/resources/lb_mgr.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/resources/lb_svc.xml b/cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/resources/lb_svc.xml new file mode 100644 index 0000000000..73054f593a --- /dev/null +++ b/cosmic-core/plugins/network-elements/internal-loadbalancer/src/test/resources/lb_svc.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/pom.xml b/cosmic-core/plugins/network-elements/nicira-nvp/pom.xml new file mode 100644 index 0000000000..d7d59842f7 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/pom.xml @@ -0,0 +1,53 @@ + + 4.0.0 + cloud-plugin-network-nvp + Cosmic Plugin - Network Nicira NVP + + cloud.cosmic + cosmic-plugins + 5.1.0.0-SNAPSHOT + ../../pom.xml + + + + + cloud.cosmic + cloud-utils + 5.1.0.0-SNAPSHOT + test-jar + test + + + + + + + org.apache.maven.plugins + maven-pmd-plugin + + + + + + + integration + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + + + + + + + diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/ConfigurePortForwardingRulesOnLogicalRouterAnswer.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/ConfigurePortForwardingRulesOnLogicalRouterAnswer.java new file mode 100644 index 0000000000..1765a81481 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/ConfigurePortForwardingRulesOnLogicalRouterAnswer.java @@ -0,0 +1,35 @@ +// +// 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.agent.api; + +/** + * + */ +public class ConfigurePortForwardingRulesOnLogicalRouterAnswer extends Answer { + + public ConfigurePortForwardingRulesOnLogicalRouterAnswer(final Command command, final boolean success, final String details) { + super(command, success, details); + } + + public ConfigurePortForwardingRulesOnLogicalRouterAnswer(final Command command, final Exception e) { + super(command, e); + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/ConfigurePortForwardingRulesOnLogicalRouterCommand.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/ConfigurePortForwardingRulesOnLogicalRouterCommand.java new file mode 100644 index 0000000000..133714e35f --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/ConfigurePortForwardingRulesOnLogicalRouterCommand.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 com.cloud.agent.api; + +import java.util.List; + +import com.cloud.agent.api.to.PortForwardingRuleTO; + +/** + * + */ +public class ConfigurePortForwardingRulesOnLogicalRouterCommand extends Command { + + private String logicalRouterUuid; + private List rules; + + public ConfigurePortForwardingRulesOnLogicalRouterCommand(final String logicalRouterUuid, final List rules) { + this.logicalRouterUuid = logicalRouterUuid; + this.rules = rules; + } + + public String getLogicalRouterUuid() { + return logicalRouterUuid; + } + + public void setLogicalRouterUuid(final String logicalRouterUuid) { + this.logicalRouterUuid = logicalRouterUuid; + } + + public List getRules() { + return rules; + } + + public void setRules(final List rules) { + this.rules = rules; + } + + /* (non-Javadoc) + * @see com.cloud.agent.api.Command#executeInSequence() + */ + @Override + public boolean executeInSequence() { + return false; + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/ConfigurePublicIpsOnLogicalRouterAnswer.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/ConfigurePublicIpsOnLogicalRouterAnswer.java new file mode 100644 index 0000000000..9da6caf8b6 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/ConfigurePublicIpsOnLogicalRouterAnswer.java @@ -0,0 +1,32 @@ +// +// 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.agent.api; + +public class ConfigurePublicIpsOnLogicalRouterAnswer extends Answer { + + public ConfigurePublicIpsOnLogicalRouterAnswer(final Command command, final boolean success, final String details) { + super(command, success, details); + } + + public ConfigurePublicIpsOnLogicalRouterAnswer(final Command command, final Exception e) { + super(command, e); + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/ConfigurePublicIpsOnLogicalRouterCommand.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/ConfigurePublicIpsOnLogicalRouterCommand.java new file mode 100644 index 0000000000..ff81ae4657 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/ConfigurePublicIpsOnLogicalRouterCommand.java @@ -0,0 +1,66 @@ +// +// 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.agent.api; + +import java.util.List; + +public class ConfigurePublicIpsOnLogicalRouterCommand extends Command { + + private String logicalRouterUuid; + private String l3GatewayServiceUuid; + private List publicCidrs; + + public ConfigurePublicIpsOnLogicalRouterCommand(final String logicalRouterUuid, final String l3GatewayServiceUuid, final List publicCidrs) { + super(); + this.logicalRouterUuid = logicalRouterUuid; + this.publicCidrs = publicCidrs; + this.l3GatewayServiceUuid = l3GatewayServiceUuid; + } + + public String getLogicalRouterUuid() { + return logicalRouterUuid; + } + + public void setLogicalRouterUuid(final String logicalRouterUuid) { + this.logicalRouterUuid = logicalRouterUuid; + } + + public String getL3GatewayServiceUuid() { + return l3GatewayServiceUuid; + } + + public void setL3GatewayServiceUuid(final String l3GatewayServiceUuid) { + this.l3GatewayServiceUuid = l3GatewayServiceUuid; + } + + public List getPublicCidrs() { + return publicCidrs; + } + + public void setPublicCidrs(final List publicCidrs) { + this.publicCidrs = publicCidrs; + } + + @Override + public boolean executeInSequence() { + return false; + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/ConfigureStaticNatRulesOnLogicalRouterAnswer.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/ConfigureStaticNatRulesOnLogicalRouterAnswer.java new file mode 100644 index 0000000000..907449170f --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/ConfigureStaticNatRulesOnLogicalRouterAnswer.java @@ -0,0 +1,44 @@ +// +// 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.agent.api; + +/** + * + */ +public class ConfigureStaticNatRulesOnLogicalRouterAnswer extends Answer { + + /** + * @param command + * @param success + * @param details + */ + public ConfigureStaticNatRulesOnLogicalRouterAnswer(final Command command, final boolean success, final String details) { + super(command, success, details); + } + + /** + * @param command + * @param e + */ + public ConfigureStaticNatRulesOnLogicalRouterAnswer(final Command command, final Exception e) { + super(command, e); + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/ConfigureStaticNatRulesOnLogicalRouterCommand.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/ConfigureStaticNatRulesOnLogicalRouterCommand.java new file mode 100644 index 0000000000..b5434b34e1 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/ConfigureStaticNatRulesOnLogicalRouterCommand.java @@ -0,0 +1,65 @@ +// +// 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.agent.api; + +import java.util.List; + +import com.cloud.agent.api.to.StaticNatRuleTO; + +/** + * + */ +public class ConfigureStaticNatRulesOnLogicalRouterCommand extends Command { + + private String logicalRouterUuid; + private List rules; + + public ConfigureStaticNatRulesOnLogicalRouterCommand(final String logicalRouterUuid, final List rules) { + super(); + this.logicalRouterUuid = logicalRouterUuid; + this.rules = rules; + + } + + public String getLogicalRouterUuid() { + return logicalRouterUuid; + } + + public void setLogicalRouterUuid(final String logicalRouterUuid) { + this.logicalRouterUuid = logicalRouterUuid; + } + + public List getRules() { + return rules; + } + + public void setRules(final List rules) { + this.rules = rules; + } + + /* (non-Javadoc) + * @see com.cloud.agent.api.Command#executeInSequence() + */ + @Override + public boolean executeInSequence() { + return false; + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/CreateLogicalRouterAnswer.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/CreateLogicalRouterAnswer.java new file mode 100644 index 0000000000..0172891d4d --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/CreateLogicalRouterAnswer.java @@ -0,0 +1,42 @@ +// +// 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.agent.api; + +/** + * + */ +public class CreateLogicalRouterAnswer extends Answer { + + private String logicalRouterUuid; + + public CreateLogicalRouterAnswer(final Command command, final boolean success, final String details, final String logicalRouterUuid) { + super(command, success, details); + this.logicalRouterUuid = logicalRouterUuid; + } + + public CreateLogicalRouterAnswer(final Command command, final Exception e) { + super(command, e); + } + + public String getLogicalRouterUuid() { + return logicalRouterUuid; + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/CreateLogicalRouterCommand.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/CreateLogicalRouterCommand.java new file mode 100644 index 0000000000..ff12b8d586 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/CreateLogicalRouterCommand.java @@ -0,0 +1,116 @@ +// +// 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.agent.api; + +/** + * + */ +public class CreateLogicalRouterCommand extends Command { + private String gatewayServiceUuid; + private String logicalSwitchUuid; + private long vlanId; + private String name; + private String ownerName; + private String publicIpCidr; + private String publicNextHop; + private String internalIpCidr; + + public CreateLogicalRouterCommand(final String gatewayServiceUuid, final long vlanId, final String logicalSwitchUuid, final String name, final String publicIpCidr, + final String publicNextHop, final String internalIpCidr, final String ownerName) { + super(); + this.gatewayServiceUuid = gatewayServiceUuid; + this.logicalSwitchUuid = logicalSwitchUuid; + this.vlanId = vlanId; + this.name = name; + this.ownerName = ownerName; + this.publicIpCidr = publicIpCidr; + this.publicNextHop = publicNextHop; + this.internalIpCidr = internalIpCidr; + } + + @Override + public boolean executeInSequence() { + return false; + } + + public String getGatewayServiceUuid() { + return gatewayServiceUuid; + } + + public void setGatewayServiceUuid(final String gatewayServiceUuid) { + this.gatewayServiceUuid = gatewayServiceUuid; + } + + public String getLogicalSwitchUuid() { + return logicalSwitchUuid; + } + + public void setLogicalSwitchUuid(final String logicalSwitchUuid) { + this.logicalSwitchUuid = logicalSwitchUuid; + } + + public long getVlanId() { + return vlanId; + } + + public void setVlanId(final long vlanId) { + this.vlanId = vlanId; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public String getOwnerName() { + return ownerName; + } + + public void setOwnerName(final String ownerName) { + this.ownerName = ownerName; + } + + public String getPublicIpCidr() { + return publicIpCidr; + } + + public void setPublicIpCidr(final String publicIpCidr) { + this.publicIpCidr = publicIpCidr; + } + + public String getInternalIpCidr() { + return internalIpCidr; + } + + public void setInternalIpCidr(final String internalIpCidr) { + this.internalIpCidr = internalIpCidr; + } + + public String getPublicNextHop() { + return publicNextHop; + } + + public void setPublicNextHop(final String publicNextHop) { + this.publicNextHop = publicNextHop; + } +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/CreateLogicalSwitchAnswer.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/CreateLogicalSwitchAnswer.java new file mode 100644 index 0000000000..ee9924e79a --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/CreateLogicalSwitchAnswer.java @@ -0,0 +1,38 @@ +// +// 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.agent.api; + +public class CreateLogicalSwitchAnswer extends Answer { + private String logicalSwitchUuid; + + public CreateLogicalSwitchAnswer(final Command command, final boolean success, final String details, final String logicalSwitchUuid) { + super(command, success, details); + this.logicalSwitchUuid = logicalSwitchUuid; + } + + public CreateLogicalSwitchAnswer(final Command command, final Exception e) { + super(command, e); + } + + public String getLogicalSwitchUuid() { + return logicalSwitchUuid; + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/CreateLogicalSwitchCommand.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/CreateLogicalSwitchCommand.java new file mode 100644 index 0000000000..5dc8616034 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/CreateLogicalSwitchCommand.java @@ -0,0 +1,57 @@ +// +// 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.agent.api; + +public class CreateLogicalSwitchCommand extends Command { + + private final String transportUuid; + private final String transportType; + private final String name; + private final String ownerName; + + public CreateLogicalSwitchCommand(final String transportUuid, final String transportType, final String name, final String ownerName) { + this.transportUuid = transportUuid; + this.transportType = transportType; + this.name = name; + this.ownerName = ownerName; + } + + @Override + public boolean executeInSequence() { + return false; + } + + public String getTransportUuid() { + return transportUuid; + } + + public String getTransportType() { + return transportType; + } + + public String getName() { + return name; + } + + public String getOwnerName() { + return ownerName; + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/CreateLogicalSwitchPortAnswer.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/CreateLogicalSwitchPortAnswer.java new file mode 100644 index 0000000000..8b093897e4 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/CreateLogicalSwitchPortAnswer.java @@ -0,0 +1,38 @@ +// +// 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.agent.api; + +public class CreateLogicalSwitchPortAnswer extends Answer { + private String logicalSwitchPortUuid; + + public CreateLogicalSwitchPortAnswer(final Command command, final boolean success, final String details, final String localSwitchPortUuid) { + super(command, success, details); + logicalSwitchPortUuid = localSwitchPortUuid; + } + + public String getLogicalSwitchPortUuid() { + return logicalSwitchPortUuid; + } + + public CreateLogicalSwitchPortAnswer(final Command command, final Exception e) { + super(command, e); + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/CreateLogicalSwitchPortCommand.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/CreateLogicalSwitchPortCommand.java new file mode 100644 index 0000000000..6bc578fae6 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/CreateLogicalSwitchPortCommand.java @@ -0,0 +1,56 @@ +// +// 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.agent.api; + +public class CreateLogicalSwitchPortCommand extends Command { + private final String logicalSwitchUuid; + private final String attachmentUuid; + private final String ownerName; + private final String nicName; + + public CreateLogicalSwitchPortCommand(final String logicalSwitchUuid, final String attachmentUuid, final String ownerName, final String nicName) { + this.logicalSwitchUuid = logicalSwitchUuid; + this.attachmentUuid = attachmentUuid; + this.ownerName = ownerName; + this.nicName = nicName; + } + + public String getLogicalSwitchUuid() { + return logicalSwitchUuid; + } + + public String getAttachmentUuid() { + return attachmentUuid; + } + + public String getOwnerName() { + return ownerName; + } + + public String getNicName() { + return nicName; + } + + @Override + public boolean executeInSequence() { + return false; + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/DeleteLogicalRouterAnswer.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/DeleteLogicalRouterAnswer.java new file mode 100644 index 0000000000..f7f101d029 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/DeleteLogicalRouterAnswer.java @@ -0,0 +1,34 @@ +// +// 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.agent.api; + +/** + * + */ +public class DeleteLogicalRouterAnswer extends Answer { + + public DeleteLogicalRouterAnswer(final Command command, final boolean success, final String details) { + super(command, success, details); + } + + public DeleteLogicalRouterAnswer(final Command command, final Exception e) { + super(command, e); + } +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/DeleteLogicalRouterCommand.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/DeleteLogicalRouterCommand.java new file mode 100644 index 0000000000..8fd14e95ef --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/DeleteLogicalRouterCommand.java @@ -0,0 +1,44 @@ +// +// 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.agent.api; + +/** + * + */ +public class DeleteLogicalRouterCommand extends Command { + + private final String logicalRouterUuid; + + public DeleteLogicalRouterCommand(String logicalRouterUuid) { + this.logicalRouterUuid = logicalRouterUuid; + } + + /* (non-Javadoc) + * @see com.cloud.agent.api.Command#executeInSequence() + */ + @Override + public boolean executeInSequence() { + return false; + } + + public String getLogicalRouterUuid() { + return logicalRouterUuid; + } +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/DeleteLogicalSwitchAnswer.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/DeleteLogicalSwitchAnswer.java new file mode 100644 index 0000000000..e2c483f8e5 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/DeleteLogicalSwitchAnswer.java @@ -0,0 +1,32 @@ +// +// 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.agent.api; + +public class DeleteLogicalSwitchAnswer extends Answer { + + public DeleteLogicalSwitchAnswer(final Command command, final boolean success, final String details) { + super(command, success, details); + } + + public DeleteLogicalSwitchAnswer(final Command command, final Exception e) { + super(command, e); + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/DeleteLogicalSwitchCommand.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/DeleteLogicalSwitchCommand.java new file mode 100644 index 0000000000..dbfefe2e7a --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/DeleteLogicalSwitchCommand.java @@ -0,0 +1,38 @@ +// +// 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.agent.api; + +public class DeleteLogicalSwitchCommand extends Command { + + private final String logicalSwitchUuid; + + public DeleteLogicalSwitchCommand(final String logicalSwitchUuid) { + this.logicalSwitchUuid = logicalSwitchUuid; + } + + @Override + public boolean executeInSequence() { + return false; + } + + public String getLogicalSwitchUuid() { + return logicalSwitchUuid; + } +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/DeleteLogicalSwitchPortAnswer.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/DeleteLogicalSwitchPortAnswer.java new file mode 100644 index 0000000000..1cd87ea90d --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/DeleteLogicalSwitchPortAnswer.java @@ -0,0 +1,32 @@ +// +// 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.agent.api; + +public class DeleteLogicalSwitchPortAnswer extends Answer { + + public DeleteLogicalSwitchPortAnswer(final Command command, final boolean success, final String details) { + super(command, success, details); + } + + public DeleteLogicalSwitchPortAnswer(final Command command, final Exception e) { + super(command, e); + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/DeleteLogicalSwitchPortCommand.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/DeleteLogicalSwitchPortCommand.java new file mode 100644 index 0000000000..4a5e6076d5 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/DeleteLogicalSwitchPortCommand.java @@ -0,0 +1,44 @@ +// +// 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.agent.api; + +public class DeleteLogicalSwitchPortCommand extends Command { + private final String logicalSwitchUuid; + private final String logicalSwithPortUuid; + + public DeleteLogicalSwitchPortCommand(final String logicalSwitchUuid, final String logicalSwitchPortUuid) { + this.logicalSwitchUuid = logicalSwitchUuid; + logicalSwithPortUuid = logicalSwitchPortUuid; + } + + public String getLogicalSwitchUuid() { + return logicalSwitchUuid; + } + + public String getLogicalSwitchPortUuid() { + return logicalSwithPortUuid; + } + + @Override + public boolean executeInSequence() { + return false; + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/FindLogicalSwitchAnswer.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/FindLogicalSwitchAnswer.java new file mode 100644 index 0000000000..46cc827670 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/FindLogicalSwitchAnswer.java @@ -0,0 +1,18 @@ +package com.cloud.agent.api; + +public class FindLogicalSwitchAnswer extends Answer { + private String logicalSwitchPortUuid; + + public FindLogicalSwitchAnswer(final Command command, final boolean success, final String details, final String localSwitchPortUuid) { + super(command, success, details); + logicalSwitchPortUuid = localSwitchPortUuid; + } + + public String getLogicalSwitchPortUuid() { + return logicalSwitchPortUuid; + } + + public FindLogicalSwitchAnswer(final Command command, final Exception e) { + super(command, e); + } +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/FindLogicalSwitchCommand.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/FindLogicalSwitchCommand.java new file mode 100644 index 0000000000..bcf263711d --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/FindLogicalSwitchCommand.java @@ -0,0 +1,19 @@ +package com.cloud.agent.api; + +public class FindLogicalSwitchCommand extends Command { + private final String logicalSwitchUuid; + + public FindLogicalSwitchCommand(String logicalSwitchUuid) { + this.logicalSwitchUuid = logicalSwitchUuid; + } + + public String getLogicalSwitchUuid() { + return logicalSwitchUuid; + } + + @Override + public boolean executeInSequence() { + return false; + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/FindLogicalSwitchPortAnswer.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/FindLogicalSwitchPortAnswer.java new file mode 100644 index 0000000000..0524531abe --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/FindLogicalSwitchPortAnswer.java @@ -0,0 +1,38 @@ +// +// 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.agent.api; + +public class FindLogicalSwitchPortAnswer extends Answer { + private String logicalSwitchPortUuid; + + public FindLogicalSwitchPortAnswer(final Command command, final boolean success, final String details, final String localSwitchPortUuid) { + super(command, success, details); + logicalSwitchPortUuid = localSwitchPortUuid; + } + + public String getLogicalSwitchPortUuid() { + return logicalSwitchPortUuid; + } + + public FindLogicalSwitchPortAnswer(final Command command, final Exception e) { + super(command, e); + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/FindLogicalSwitchPortCommand.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/FindLogicalSwitchPortCommand.java new file mode 100644 index 0000000000..ee00bd24a3 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/FindLogicalSwitchPortCommand.java @@ -0,0 +1,44 @@ +// +// 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.agent.api; + +public class FindLogicalSwitchPortCommand extends Command { + private final String logicalSwitchUuid; + private final String logicalSwitchPortUuid; + + public FindLogicalSwitchPortCommand(String logicalSwitchUuid, String logicalSwitchPortUuid) { + this.logicalSwitchUuid = logicalSwitchUuid; + this.logicalSwitchPortUuid = logicalSwitchPortUuid; + } + + public String getLogicalSwitchUuid() { + return logicalSwitchUuid; + } + + public String getLogicalSwitchPortUuid() { + return logicalSwitchPortUuid; + } + + @Override + public boolean executeInSequence() { + return false; + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/StartupNiciraNvpCommand.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/StartupNiciraNvpCommand.java new file mode 100644 index 0000000000..9597d2f668 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/StartupNiciraNvpCommand.java @@ -0,0 +1,30 @@ +// +// 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.agent.api; + +import com.cloud.host.Host; + +public class StartupNiciraNvpCommand extends StartupCommand { + + public StartupNiciraNvpCommand() { + super(Host.Type.L2Networking); + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/UpdateLogicalSwitchPortAnswer.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/UpdateLogicalSwitchPortAnswer.java new file mode 100644 index 0000000000..14717232d7 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/UpdateLogicalSwitchPortAnswer.java @@ -0,0 +1,38 @@ +// +// 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.agent.api; + +public class UpdateLogicalSwitchPortAnswer extends Answer { + private String logicalSwitchPortUuid; + + public UpdateLogicalSwitchPortAnswer(Command command, boolean success, String details, String localSwitchPortUuid) { + super(command, success, details); + logicalSwitchPortUuid = localSwitchPortUuid; + } + + public String getLogicalSwitchPortUuid() { + return logicalSwitchPortUuid; + } + + public UpdateLogicalSwitchPortAnswer(Command command, Exception e) { + super(command, e); + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/UpdateLogicalSwitchPortCommand.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/UpdateLogicalSwitchPortCommand.java new file mode 100644 index 0000000000..91682a1aca --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/agent/api/UpdateLogicalSwitchPortCommand.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 com.cloud.agent.api; + +public class UpdateLogicalSwitchPortCommand extends Command { + private final String logicalSwitchUuid; + private final String logicalSwitchPortUuid; + private final String attachmentUuid; + private final String ownerName; + private final String nicName; + + public UpdateLogicalSwitchPortCommand(final String logicalSwitchPortUuid, final String logicalSwitchUuid, final String attachmentUuid, final String ownerName, + final String nicName) { + this.logicalSwitchUuid = logicalSwitchUuid; + this.logicalSwitchPortUuid = logicalSwitchPortUuid; + this.attachmentUuid = attachmentUuid; + this.ownerName = ownerName; + this.nicName = nicName; + } + + public String getLogicalSwitchUuid() { + return logicalSwitchUuid; + } + + public String getLogicalSwitchPortUuid() { + return logicalSwitchPortUuid; + } + + public String getAttachmentUuid() { + return attachmentUuid; + } + + public String getOwnerName() { + return ownerName; + } + + public String getNicName() { + return nicName; + } + + @Override + public boolean executeInSequence() { + return false; + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/api/commands/AddNiciraNvpDeviceCmd.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/api/commands/AddNiciraNvpDeviceCmd.java new file mode 100644 index 0000000000..5b4477a1ce --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/api/commands/AddNiciraNvpDeviceCmd.java @@ -0,0 +1,154 @@ +// +// 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.api.commands; + +import javax.inject.Inject; + +import com.cloud.api.response.NiciraNvpDeviceResponse; +import com.cloud.event.EventTypes; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.NiciraNvpDeviceVO; +import com.cloud.network.element.NiciraNvpElementService; +import com.cloud.utils.exception.CloudRuntimeException; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.PhysicalNetworkResponse; +import org.apache.cloudstack.context.CallContext; + +@APICommand(name = "addNiciraNvpDevice", responseObject = NiciraNvpDeviceResponse.class, description = "Adds a Nicira NVP device", + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class AddNiciraNvpDeviceCmd extends BaseAsyncCmd { + private static final String s_name = "addniciranvpdeviceresponse"; + @Inject + protected NiciraNvpElementService niciraNvpElementService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.PHYSICAL_NETWORK_ID, + type = CommandType.UUID, + entityType = PhysicalNetworkResponse.class, + required = true, + description = "the Physical Network ID") + private Long physicalNetworkId; + + @Parameter(name = ApiConstants.HOST_NAME, type = CommandType.STRING, required = true, description = "Hostname of ip address of the Nicira NVP Controller.") + private String host; + + @Parameter(name = ApiConstants.USERNAME, type = CommandType.STRING, required = true, description = "Credentials to access the Nicira Controller API") + private String username; + + @Parameter(name = ApiConstants.PASSWORD, type = CommandType.STRING, required = true, description = "Credentials to access the Nicira Controller API") + private String password; + + @Parameter(name = ApiConstants.NICIRA_NVP_TRANSPORT_ZONE_UUID, + type = CommandType.STRING, + required = true, + description = "The Transportzone UUID configured on the Nicira Controller") + private String transportzoneuuid; + + @Parameter(name = ApiConstants.NICIRA_NVP_GATEWAYSERVICE_UUID, + type = CommandType.STRING, + required = false, + description = "The L3 Gateway Service UUID configured on the Nicira Controller") + private String l3gatewayserviceuuid; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getPhysicalNetworkId() { + return physicalNetworkId; + } + + public String getHost() { + return host; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public String getTransportzoneUuid() { + return transportzoneuuid; + } + + public String getL3GatewayServiceUuid() { + return l3gatewayserviceuuid; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, + ResourceAllocationException { + try { + NiciraNvpDeviceVO niciraNvpDeviceVO = niciraNvpElementService.addNiciraNvpDevice(this); + if (niciraNvpDeviceVO != null) { + NiciraNvpDeviceResponse response = niciraNvpElementService.createNiciraNvpDeviceResponse(niciraNvpDeviceVO); + response.setObjectName("niciranvpdevice"); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to add Nicira NVP device due to internal error."); + } + } catch (InvalidParameterValueException invalidParamExcp) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, invalidParamExcp.getMessage()); + } catch (CloudRuntimeException runtimeExcp) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, runtimeExcp.getMessage()); + } + } + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_EXTERNAL_NVP_CONTROLLER_ADD; + } + + @Override + public String getEventDescription() { + return "Adding a Nicira Nvp Controller"; + } +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/api/commands/DeleteNiciraNvpDeviceCmd.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/api/commands/DeleteNiciraNvpDeviceCmd.java new file mode 100644 index 0000000000..300636fb0d --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/api/commands/DeleteNiciraNvpDeviceCmd.java @@ -0,0 +1,112 @@ +// +// 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.api.commands; + +import javax.inject.Inject; + +import com.cloud.api.response.NiciraNvpDeviceResponse; +import com.cloud.event.EventTypes; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.element.NiciraNvpElementService; +import com.cloud.utils.exception.CloudRuntimeException; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; + +@APICommand(name = "deleteNiciraNvpDevice", responseObject = SuccessResponse.class, description = " delete a nicira nvp device", + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class DeleteNiciraNvpDeviceCmd extends BaseAsyncCmd { + private static final String s_name = "deleteniciranvpdeviceresponse"; + @Inject + protected NiciraNvpElementService niciraNvpElementService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.NICIRA_NVP_DEVICE_ID, + type = CommandType.UUID, + entityType = NiciraNvpDeviceResponse.class, + required = true, + description = "Nicira device ID") + private Long niciraNvpDeviceId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getNiciraNvpDeviceId() { + return niciraNvpDeviceId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, + ResourceAllocationException { + try { + boolean result = niciraNvpElementService.deleteNiciraNvpDevice(this); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete Nicira device."); + } + } catch (InvalidParameterValueException invalidParamExcp) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, invalidParamExcp.getMessage()); + } catch (CloudRuntimeException runtimeExcp) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, runtimeExcp.getMessage()); + } + } + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_EXTERNAL_NVP_CONTROLLER_DELETE; + } + + @Override + public String getEventDescription() { + return "Deleting Nicira Nvp Controller"; + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/api/commands/ListNiciraNvpDeviceNetworksCmd.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/api/commands/ListNiciraNvpDeviceNetworksCmd.java new file mode 100644 index 0000000000..22f11e9d14 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/api/commands/ListNiciraNvpDeviceNetworksCmd.java @@ -0,0 +1,111 @@ +// +// 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.api.commands; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import com.cloud.api.response.NiciraNvpDeviceResponse; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Network; +import com.cloud.network.element.NiciraNvpElementService; +import com.cloud.utils.exception.CloudRuntimeException; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ResponseObject.ResponseView; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.NetworkResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@APICommand(name = "listNiciraNvpDeviceNetworks", responseObject = NetworkResponse.class, description = "lists network that are using a nicira nvp device", + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class ListNiciraNvpDeviceNetworksCmd extends BaseListCmd { + + public static final Logger s_logger = LoggerFactory.getLogger(ListNiciraNvpDeviceNetworksCmd.class.getName()); + private static final String s_name = "listniciranvpdevicenetworks"; + @Inject + protected NiciraNvpElementService niciraNvpElementService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.NICIRA_NVP_DEVICE_ID, + type = CommandType.UUID, + entityType = NiciraNvpDeviceResponse.class, + required = true, + description = "nicira nvp device ID") + private Long niciraNvpDeviceId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getNiciraNvpDeviceId() { + return niciraNvpDeviceId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, + ResourceAllocationException { + try { + List networks = niciraNvpElementService.listNiciraNvpDeviceNetworks(this); + ListResponse response = new ListResponse(); + List networkResponses = new ArrayList(); + + if (networks != null && !networks.isEmpty()) { + for (Network network : networks) { + NetworkResponse networkResponse = _responseGenerator.createNetworkResponse(ResponseView.Full, network); + networkResponses.add(networkResponse); + } + } + + response.setResponses(networkResponses); + response.setResponseName(getCommandName()); + setResponseObject(response); + } catch (InvalidParameterValueException invalidParamExcp) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, invalidParamExcp.getMessage()); + } catch (CloudRuntimeException runtimeExcp) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, runtimeExcp.getMessage()); + } + } + + @Override + public String getCommandName() { + return s_name; + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/api/commands/ListNiciraNvpDevicesCmd.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/api/commands/ListNiciraNvpDevicesCmd.java new file mode 100644 index 0000000000..78a2613851 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/api/commands/ListNiciraNvpDevicesCmd.java @@ -0,0 +1,108 @@ +// +// 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.api.commands; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import com.cloud.api.response.NiciraNvpDeviceResponse; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.NiciraNvpDeviceVO; +import com.cloud.network.element.NiciraNvpElementService; +import com.cloud.utils.exception.CloudRuntimeException; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.PhysicalNetworkResponse; + +@APICommand(name = "listNiciraNvpDevices", responseObject = NiciraNvpDeviceResponse.class, description = "Lists Nicira NVP devices", + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class ListNiciraNvpDevicesCmd extends BaseListCmd { + private static final String s_name = "listniciranvpdeviceresponse"; + + @Inject + protected NiciraNvpElementService niciraNvpElementService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.PHYSICAL_NETWORK_ID, type = CommandType.UUID, entityType = PhysicalNetworkResponse.class, description = "the Physical Network ID") + private Long physicalNetworkId; + + @Parameter(name = ApiConstants.NICIRA_NVP_DEVICE_ID, type = CommandType.UUID, entityType = NiciraNvpDeviceResponse.class, description = "nicira nvp device ID") + private Long niciraNvpDeviceId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getNiciraNvpDeviceId() { + return niciraNvpDeviceId; + } + + public Long getPhysicalNetworkId() { + return physicalNetworkId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { + try { + List niciraDevices = niciraNvpElementService.listNiciraNvpDevices(this); + ListResponse response = new ListResponse(); + List niciraDevicesResponse = new ArrayList(); + + if (niciraDevices != null && !niciraDevices.isEmpty()) { + for (NiciraNvpDeviceVO niciraDeviceVO : niciraDevices) { + NiciraNvpDeviceResponse niciraDeviceResponse = niciraNvpElementService.createNiciraNvpDeviceResponse(niciraDeviceVO); + niciraDevicesResponse.add(niciraDeviceResponse); + } + } + + response.setResponses(niciraDevicesResponse); + response.setResponseName(getCommandName()); + setResponseObject(response); + } catch (InvalidParameterValueException invalidParamExcp) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, invalidParamExcp.getMessage()); + } catch (CloudRuntimeException runtimeExcp) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, runtimeExcp.getMessage()); + } + } + + @Override + public String getCommandName() { + return s_name; + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/api/response/NiciraNvpDeviceResponse.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/api/response/NiciraNvpDeviceResponse.java new file mode 100644 index 0000000000..33299dc529 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/api/response/NiciraNvpDeviceResponse.java @@ -0,0 +1,88 @@ +// +// 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.api.response; + +import com.cloud.network.NiciraNvpDeviceVO; +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; + +@EntityReference(value = NiciraNvpDeviceVO.class) +public class NiciraNvpDeviceResponse extends BaseResponse { + @SerializedName(ApiConstants.NICIRA_NVP_DEVICE_ID) + @Param(description = "device id of the Nicire Nvp") + private String id; + + @SerializedName(ApiConstants.PHYSICAL_NETWORK_ID) + @Param(description = "the physical network to which this Nirica Nvp belongs to") + private String physicalNetworkId; + + @SerializedName(ApiConstants.PROVIDER) + @Param(description = "name of the provider") + private String providerName; + + @SerializedName(ApiConstants.NICIRA_NVP_DEVICE_NAME) + @Param(description = "device name") + private String deviceName; + + @SerializedName(ApiConstants.HOST_NAME) + @Param(description = "the controller Ip address") + private String hostName; + + @SerializedName(ApiConstants.NICIRA_NVP_TRANSPORT_ZONE_UUID) + @Param(description = "the transport zone Uuid") + private String transportZoneUuid; + + @SerializedName(ApiConstants.NICIRA_NVP_GATEWAYSERVICE_UUID) + @Param(description = "this L3 gateway service Uuid") + private String l3GatewayServiceUuid; + + public void setId(String nvpDeviceId) { + this.id = nvpDeviceId; + } + + public void setPhysicalNetworkId(final String physicalNetworkId) { + this.physicalNetworkId = physicalNetworkId; + } + + public void setProviderName(final String providerName) { + this.providerName = providerName; + } + + public void setDeviceName(final String deviceName) { + this.deviceName = deviceName; + } + + public void setHostName(final String hostName) { + this.hostName = hostName; + } + + public void setTransportZoneUuid(final String transportZoneUuid) { + this.transportZoneUuid = transportZoneUuid; + } + + public void setL3GatewayServiceUuid(final String l3GatewayServiceUuid) { + this.l3GatewayServiceUuid = l3GatewayServiceUuid; + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/NiciraNvpDeviceVO.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/NiciraNvpDeviceVO.java new file mode 100644 index 0000000000..431e8558ae --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/NiciraNvpDeviceVO.java @@ -0,0 +1,99 @@ +// +// 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.network; + +import java.util.UUID; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.apache.cloudstack.api.InternalIdentity; + +@Entity +@Table(name = "external_nicira_nvp_devices") +public class NiciraNvpDeviceVO implements InternalIdentity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "uuid") + private String uuid; + + @Column(name = "host_id") + private long hostId; + + @Column(name = "physical_network_id") + private long physicalNetworkId; + + @Column(name = "provider_name") + private String providerName; + + @Column(name = "device_name") + private String deviceName; + + public NiciraNvpDeviceVO() { + uuid = UUID.randomUUID().toString(); + } + + public NiciraNvpDeviceVO(final long hostId, final long physicalNetworkId, final String providerName, final String deviceName) { + super(); + this.hostId = hostId; + this.physicalNetworkId = physicalNetworkId; + this.providerName = providerName; + this.deviceName = deviceName; + uuid = UUID.randomUUID().toString(); + } + + @Override + public long getId() { + return id; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public long getPhysicalNetworkId() { + return physicalNetworkId; + } + + public long getHostId() { + return hostId; + } + + public String getProviderName() { + return providerName; + } + + public String getDeviceName() { + return deviceName; + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/NiciraNvpNicMappingVO.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/NiciraNvpNicMappingVO.java new file mode 100644 index 0000000000..9b137806a4 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/NiciraNvpNicMappingVO.java @@ -0,0 +1,87 @@ +// +// 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.network; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.apache.cloudstack.api.InternalIdentity; + +@Entity +@Table(name = "nicira_nvp_nic_map") +public class NiciraNvpNicMappingVO implements InternalIdentity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "logicalswitch") + private String logicalSwitchUuid; + + @Column(name = "logicalswitchport") + private String logicalSwitchPortUuid; + + @Column(name = "nic") + private String nicUuid; + + public NiciraNvpNicMappingVO() { + } + + public NiciraNvpNicMappingVO(final String logicalSwitchUuid, final String logicalSwitchPortUuid, final String nicUuid) { + this.logicalSwitchUuid = logicalSwitchUuid; + this.logicalSwitchPortUuid = logicalSwitchPortUuid; + this.nicUuid = nicUuid; + } + + public String getLogicalSwitchUuid() { + return logicalSwitchUuid; + } + + public void setLogicalSwitchUuid(final String logicalSwitchUuid) { + this.logicalSwitchUuid = logicalSwitchUuid; + } + + public String getLogicalSwitchPortUuid() { + return logicalSwitchPortUuid; + } + + public void setLogicalSwitchPortUuid(final String logicalSwitchPortUuid) { + this.logicalSwitchPortUuid = logicalSwitchPortUuid; + } + + public String getNicUuid() { + return nicUuid; + } + + public void setNicUuid(String nicUuid) { + this.nicUuid = nicUuid; + } + + @Override + public long getId() { + return id; + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/NiciraNvpRouterMappingVO.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/NiciraNvpRouterMappingVO.java new file mode 100644 index 0000000000..9fae3cd746 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/NiciraNvpRouterMappingVO.java @@ -0,0 +1,85 @@ +// +// 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.network; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.apache.cloudstack.api.InternalIdentity; + +@Entity +@Table(name = "nicira_nvp_router_map") +public class NiciraNvpRouterMappingVO implements InternalIdentity { + //FIXME the ddl for this table should be in one of the upgrade scripts + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "logicalrouter_uuid") + private String logicalRouterUuid; + + @Column(name = "network_id") + private long networkId; + + public NiciraNvpRouterMappingVO() { + } + + public NiciraNvpRouterMappingVO(final String logicalRouterUuid, final long networkId) { + this.logicalRouterUuid = logicalRouterUuid; + this.networkId = networkId; + } + + public NiciraNvpRouterMappingVO(final long id, final String logicalRouterUuid, final long networkId) { + this.id = id; + this.logicalRouterUuid = logicalRouterUuid; + this.networkId = networkId; + } + + @Override + public long getId() { + return id; + } + + public void setId(final long id) { + this.id = id; + } + + public String getLogicalRouterUuid() { + return logicalRouterUuid; + } + + public void setLogicalRouterUuid(final String logicalRouterUuid) { + this.logicalRouterUuid = logicalRouterUuid; + } + + public long getNetworkId() { + return networkId; + } + + public void setNetworkId(final long networkId) { + this.networkId = networkId; + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/dao/NiciraNvpDao.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/dao/NiciraNvpDao.java new file mode 100644 index 0000000000..f2a9754d50 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/dao/NiciraNvpDao.java @@ -0,0 +1,35 @@ +// +// 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.network.dao; + +import java.util.List; + +import com.cloud.network.NiciraNvpDeviceVO; +import com.cloud.utils.db.GenericDao; + +public interface NiciraNvpDao extends GenericDao { + /** + * list all the nicira nvp devices added in to this physical network + * @param physicalNetworkId physical Network Id + * @return list of NiciraNvpDeviceVO for this physical network. + */ + List listByPhysicalNetwork(long physicalNetworkId); + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/dao/NiciraNvpDaoImpl.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/dao/NiciraNvpDaoImpl.java new file mode 100644 index 0000000000..3183c917c2 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/dao/NiciraNvpDaoImpl.java @@ -0,0 +1,50 @@ +// +// 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.network.dao; + +import java.util.List; + +import com.cloud.network.NiciraNvpDeviceVO; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.SearchCriteria.Op; + +import org.springframework.stereotype.Component; + +@Component +public class NiciraNvpDaoImpl extends GenericDaoBase implements NiciraNvpDao { + + protected final SearchBuilder physicalNetworkIdSearch; + + public NiciraNvpDaoImpl() { + physicalNetworkIdSearch = createSearchBuilder(); + physicalNetworkIdSearch.and("physicalNetworkId", physicalNetworkIdSearch.entity().getPhysicalNetworkId(), Op.EQ); + physicalNetworkIdSearch.done(); + } + + @Override + public List listByPhysicalNetwork(final long physicalNetworkId) { + SearchCriteria sc = physicalNetworkIdSearch.create(); + sc.setParameters("physicalNetworkId", physicalNetworkId); + return search(sc, null); + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/dao/NiciraNvpNicMappingDao.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/dao/NiciraNvpNicMappingDao.java new file mode 100644 index 0000000000..ff81b7598e --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/dao/NiciraNvpNicMappingDao.java @@ -0,0 +1,32 @@ +// +// 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.network.dao; + +import com.cloud.network.NiciraNvpNicMappingVO; +import com.cloud.utils.db.GenericDao; + +public interface NiciraNvpNicMappingDao extends GenericDao { + + /** find the mapping for a nic + * @param nicUuid the Uuid of a nic attached to a logical switch port + * @return NiciraNvpNicMapping for this nic uuid or null if it does not exist + */ + public NiciraNvpNicMappingVO findByNicUuid(String nicUuid); +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/dao/NiciraNvpNicMappingDaoImpl.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/dao/NiciraNvpNicMappingDaoImpl.java new file mode 100644 index 0000000000..7090280253 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/dao/NiciraNvpNicMappingDaoImpl.java @@ -0,0 +1,48 @@ +// +// 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.network.dao; + +import com.cloud.network.NiciraNvpNicMappingVO; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.SearchCriteria.Op; + +import org.springframework.stereotype.Component; + +@Component +public class NiciraNvpNicMappingDaoImpl extends GenericDaoBase implements NiciraNvpNicMappingDao { + + protected final SearchBuilder nicSearch; + + public NiciraNvpNicMappingDaoImpl() { + nicSearch = createSearchBuilder(); + nicSearch.and("nicUuid", nicSearch.entity().getNicUuid(), Op.EQ); + nicSearch.done(); + } + + @Override + public NiciraNvpNicMappingVO findByNicUuid(final String nicUuid) { + SearchCriteria sc = nicSearch.create(); + sc.setParameters("nicUuid", nicUuid); + return findOneBy(sc); + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/dao/NiciraNvpRouterMappingDao.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/dao/NiciraNvpRouterMappingDao.java new file mode 100644 index 0000000000..b64fb90b9a --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/dao/NiciraNvpRouterMappingDao.java @@ -0,0 +1,28 @@ +// +// 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.network.dao; + +import com.cloud.network.NiciraNvpRouterMappingVO; +import com.cloud.utils.db.GenericDao; + +public interface NiciraNvpRouterMappingDao extends GenericDao { + + public NiciraNvpRouterMappingVO findByNetworkId(long id); +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/dao/NiciraNvpRouterMappingDaoImpl.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/dao/NiciraNvpRouterMappingDaoImpl.java new file mode 100644 index 0000000000..a7156c0acf --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/dao/NiciraNvpRouterMappingDaoImpl.java @@ -0,0 +1,48 @@ +// +// 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.network.dao; + +import com.cloud.network.NiciraNvpRouterMappingVO; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.SearchCriteria.Op; + +import org.springframework.stereotype.Component; + +@Component +public class NiciraNvpRouterMappingDaoImpl extends GenericDaoBase implements NiciraNvpRouterMappingDao { + + protected final SearchBuilder networkSearch; + + public NiciraNvpRouterMappingDaoImpl() { + networkSearch = createSearchBuilder(); + networkSearch.and("network_id", networkSearch.entity().getNetworkId(), Op.EQ); + networkSearch.done(); + } + + @Override + public NiciraNvpRouterMappingVO findByNetworkId(final long id) { + SearchCriteria sc = networkSearch.create(); + sc.setParameters("network_id", id); + return findOneBy(sc); + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/element/NiciraNvpElement.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/element/NiciraNvpElement.java new file mode 100644 index 0000000000..0640424364 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/element/NiciraNvpElement.java @@ -0,0 +1,847 @@ +// +// 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.network.element; + +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.ConfigurePortForwardingRulesOnLogicalRouterAnswer; +import com.cloud.agent.api.ConfigurePortForwardingRulesOnLogicalRouterCommand; +import com.cloud.agent.api.ConfigurePublicIpsOnLogicalRouterAnswer; +import com.cloud.agent.api.ConfigurePublicIpsOnLogicalRouterCommand; +import com.cloud.agent.api.ConfigureStaticNatRulesOnLogicalRouterAnswer; +import com.cloud.agent.api.ConfigureStaticNatRulesOnLogicalRouterCommand; +import com.cloud.agent.api.CreateLogicalRouterAnswer; +import com.cloud.agent.api.CreateLogicalRouterCommand; +import com.cloud.agent.api.CreateLogicalSwitchPortAnswer; +import com.cloud.agent.api.CreateLogicalSwitchPortCommand; +import com.cloud.agent.api.DeleteLogicalRouterAnswer; +import com.cloud.agent.api.DeleteLogicalRouterCommand; +import com.cloud.agent.api.DeleteLogicalSwitchPortAnswer; +import com.cloud.agent.api.DeleteLogicalSwitchPortCommand; +import com.cloud.agent.api.FindLogicalSwitchPortAnswer; +import com.cloud.agent.api.FindLogicalSwitchPortCommand; +import com.cloud.agent.api.StartupCommand; +import com.cloud.agent.api.StartupNiciraNvpCommand; +import com.cloud.agent.api.UpdateLogicalSwitchPortCommand; +import com.cloud.agent.api.to.PortForwardingRuleTO; +import com.cloud.agent.api.to.StaticNatRuleTO; +import com.cloud.api.ApiDBUtils; +import com.cloud.api.commands.AddNiciraNvpDeviceCmd; +import com.cloud.api.commands.DeleteNiciraNvpDeviceCmd; +import com.cloud.api.commands.ListNiciraNvpDeviceNetworksCmd; +import com.cloud.api.commands.ListNiciraNvpDevicesCmd; +import com.cloud.api.response.NiciraNvpDeviceResponse; +import com.cloud.configuration.ConfigurationManager; +import com.cloud.dc.Vlan; +import com.cloud.dc.dao.VlanDao; +import com.cloud.deploy.DeployDestination; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.IllegalVirtualMachineException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.NicPreparationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.host.DetailVO; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.host.dao.HostDetailsDao; +import com.cloud.network.IpAddress; +import com.cloud.network.IpAddressManager; +import com.cloud.network.Network; +import com.cloud.network.Network.Capability; +import com.cloud.network.Network.Provider; +import com.cloud.network.Network.Service; +import com.cloud.network.NetworkModel; +import com.cloud.network.Networks; +import com.cloud.network.Networks.BroadcastDomainType; +import com.cloud.network.NiciraNvpDeviceVO; +import com.cloud.network.NiciraNvpNicMappingVO; +import com.cloud.network.NiciraNvpRouterMappingVO; +import com.cloud.network.PhysicalNetwork; +import com.cloud.network.PhysicalNetworkServiceProvider; +import com.cloud.network.PublicIpAddress; +import com.cloud.network.addr.PublicIp; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkServiceMapDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.dao.NiciraNvpDao; +import com.cloud.network.dao.NiciraNvpNicMappingDao; +import com.cloud.network.dao.NiciraNvpRouterMappingDao; +import com.cloud.network.dao.PhysicalNetworkDao; +import com.cloud.network.dao.PhysicalNetworkServiceProviderDao; +import com.cloud.network.dao.PhysicalNetworkServiceProviderVO; +import com.cloud.network.dao.PhysicalNetworkVO; +import com.cloud.network.resource.NiciraNvpResource; +import com.cloud.network.rules.PortForwardingRule; +import com.cloud.network.rules.StaticNat; +import com.cloud.offering.NetworkOffering; +import com.cloud.resource.ResourceManager; +import com.cloud.resource.ResourceState; +import com.cloud.resource.ResourceStateAdapter; +import com.cloud.resource.ServerResource; +import com.cloud.resource.UnableDeleteHostException; +import com.cloud.user.Account; +import com.cloud.utils.component.AdapterBase; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallback; +import com.cloud.utils.db.TransactionStatus; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.NetUtils; +import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; +import com.cloud.vm.ReservationContext; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.NicDao; + +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.network.ExternalNetworkDeviceManager.NetworkDevice; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +@Component +public class NiciraNvpElement extends AdapterBase implements ConnectivityProvider, SourceNatServiceProvider, PortForwardingServiceProvider, StaticNatServiceProvider, +NiciraNvpElementService, ResourceStateAdapter, IpDeployer { + + private static final int MAX_PORT = 65535; + private static final int MIN_PORT = 0; + + private static final Logger s_logger = LoggerFactory.getLogger(NiciraNvpElement.class); + + private static final Map> capabilities = setCapabilities(); + + @Inject + protected NicDao nicDao; + @Inject + protected ResourceManager resourceMgr; + @Inject + protected PhysicalNetworkDao physicalNetworkDao; + @Inject + protected PhysicalNetworkServiceProviderDao physicalNetworkServiceProviderDao; + @Inject + protected NiciraNvpDao niciraNvpDao; + @Inject + protected HostDetailsDao hostDetailsDao; + @Inject + protected HostDao hostDao; + @Inject + protected AgentManager agentMgr; + @Inject + protected NiciraNvpNicMappingDao niciraNvpNicMappingDao; + @Inject + protected NiciraNvpRouterMappingDao niciraNvpRouterMappingDao; + @Inject + protected NetworkDao networkDao; + @Inject + protected NetworkOrchestrationService networkManager; + @Inject + protected NetworkModel networkModel; + @Inject + protected ConfigurationManager configMgr; + @Inject + protected NetworkServiceMapDao ntwkSrvcDao; + @Inject + protected VlanDao vlanDao; + @Inject + protected IpAddressManager ipAddrMgr; + + @Override + public Map> getCapabilities() { + return capabilities; + } + + @Override + public Provider getProvider() { + return Provider.NiciraNvp; + } + + protected boolean canHandle(Network network, Service service) { + s_logger.debug("Checking if NiciraNvpElement can handle service " + service.getName() + " on network " + network.getDisplayText()); + if (network.getBroadcastDomainType() != BroadcastDomainType.Lswitch) { + return false; + } + + if (!networkModel.isProviderForNetwork(getProvider(), network.getId())) { + s_logger.debug("NiciraNvpElement is not a provider for network " + network.getDisplayText()); + return false; + } + + if (!ntwkSrvcDao.canProviderSupportServiceInNetwork(network.getId(), service, Network.Provider.NiciraNvp)) { + s_logger.debug("NiciraNvpElement can't provide the " + service.getName() + " service on network " + network.getDisplayText()); + return false; + } + + return true; + } + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + super.configure(name, params); + resourceMgr.registerResourceStateAdapter(name, this); + return true; + } + + @Override + public boolean implement(Network network, NetworkOffering offering, DeployDestination dest, ReservationContext context) throws ConcurrentOperationException, + ResourceUnavailableException, InsufficientCapacityException { + s_logger.debug("entering NiciraNvpElement implement function for network " + network.getDisplayText() + " (state " + network.getState() + ")"); + + if (!canHandle(network, Service.Connectivity)) { + return false; + } + + if (network.getBroadcastUri() == null) { + s_logger.error("Nic has no broadcast Uri with the LSwitch Uuid"); + return false; + } + + final List devices = niciraNvpDao.listByPhysicalNetwork(network.getPhysicalNetworkId()); + if (devices.isEmpty()) { + s_logger.error("No NiciraNvp Controller on physical network " + network.getPhysicalNetworkId()); + return false; + } + final NiciraNvpDeviceVO niciraNvpDevice = devices.get(0); + final HostVO niciraNvpHost = hostDao.findById(niciraNvpDevice.getHostId()); + hostDao.loadDetails(niciraNvpHost); + + final Account owner = context.getAccount(); + + /* + * TODO Shouldn't we lock the network as we might need to do + * multiple operations that should be done only once. + */ + + // Implement SourceNat immediately as we have al the info already + if (networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.SourceNat, Provider.NiciraNvp)) { + s_logger.debug("Apparently we are supposed to provide SourceNat on this network"); + + final PublicIp sourceNatIp = ipAddrMgr.assignSourceNatIpAddressToGuestNetwork(owner, network); + final String publicCidr = sourceNatIp.getAddress().addr() + "/" + NetUtils.getCidrSize(sourceNatIp.getVlanNetmask()); + final String internalCidr = network.getGateway() + "/" + network.getCidr().split("/")[1]; + // assuming a vlan: + String vtag = sourceNatIp.getVlanTag(); + BroadcastDomainType tiep = null; + try { + tiep = BroadcastDomainType.getTypeOf(vtag); + } catch (final URISyntaxException use) { + throw new CloudRuntimeException("vlantag for sourceNatIp is not valid: " + vtag, use); + } + if (tiep == BroadcastDomainType.Vlan) { + vtag = BroadcastDomainType.Vlan.getValueFrom(BroadcastDomainType.fromString(vtag)); + } else if (!(tiep == BroadcastDomainType.UnDecided || tiep == BroadcastDomainType.Native)) { + throw new CloudRuntimeException("only vlans are supported for sourceNatIp, at this moment: " + vtag); + } + final long vlanid = Vlan.UNTAGGED.equals(vtag) ? 0 : Long.parseLong(vtag); + + final CreateLogicalRouterCommand cmd = + new CreateLogicalRouterCommand(niciraNvpHost.getDetail("l3gatewayserviceuuid"), vlanid, BroadcastDomainType.getValue(network.getBroadcastUri()), + "router-" + network.getDisplayText(), publicCidr, sourceNatIp.getGateway(), internalCidr, context.getDomain().getName() + "-" + + context.getAccount().getAccountName()); + final CreateLogicalRouterAnswer answer = (CreateLogicalRouterAnswer)agentMgr.easySend(niciraNvpHost.getId(), cmd); + if (answer.getResult() == false) { + s_logger.error("Failed to create Logical Router for network " + network.getDisplayText()); + return false; + } + + // Store the uuid so we can easily find it during cleanup + final NiciraNvpRouterMappingVO routermapping = new NiciraNvpRouterMappingVO(answer.getLogicalRouterUuid(), network.getId()); + niciraNvpRouterMappingDao.persist(routermapping); + } + + return true; + } + + @Override + public boolean prepare(Network network, NicProfile nic, VirtualMachineProfile vm, DeployDestination dest, ReservationContext context) + throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException, IllegalVirtualMachineException { + + if (!canHandle(network, Service.Connectivity)) { + return false; + } + + if (network.getBroadcastUri() == null) { + s_logger.error("Nic has no broadcast Uri with the LSwitch Uuid"); + return false; + } + + final NicVO nicVO = nicDao.findById(nic.getId()); + + final List devices = niciraNvpDao.listByPhysicalNetwork(network.getPhysicalNetworkId()); + if (devices.isEmpty()) { + s_logger.error("No NiciraNvp Controller on physical network " + network.getPhysicalNetworkId()); + return false; + } + final NiciraNvpDeviceVO niciraNvpDevice = devices.get(0); + final HostVO niciraNvpHost = hostDao.findById(niciraNvpDevice.getHostId()); + + final NiciraNvpNicMappingVO existingNicMap = niciraNvpNicMappingDao.findByNicUuid(nicVO.getUuid()); + if (existingNicMap != null) { + final FindLogicalSwitchPortCommand findCmd = new FindLogicalSwitchPortCommand(existingNicMap.getLogicalSwitchUuid(), existingNicMap.getLogicalSwitchPortUuid()); + final FindLogicalSwitchPortAnswer answer = (FindLogicalSwitchPortAnswer)agentMgr.easySend(niciraNvpHost.getId(), findCmd); + + if (answer.getResult()) { + s_logger.warn("Existing Logical Switchport found for nic " + nic.getName() + " with uuid " + existingNicMap.getLogicalSwitchPortUuid()); + final UpdateLogicalSwitchPortCommand cmd = + new UpdateLogicalSwitchPortCommand(existingNicMap.getLogicalSwitchPortUuid(), BroadcastDomainType.getValue(network.getBroadcastUri()), + nicVO.getUuid(), context.getDomain().getName() + "-" + context.getAccount().getAccountName(), nic.getName()); + agentMgr.easySend(niciraNvpHost.getId(), cmd); + return true; + } else { + s_logger.error("Stale entry found for nic " + nic.getName() + " with logical switchport uuid " + existingNicMap.getLogicalSwitchPortUuid()); + niciraNvpNicMappingDao.remove(existingNicMap.getId()); + } + } + + final CreateLogicalSwitchPortCommand cmd = + new CreateLogicalSwitchPortCommand(BroadcastDomainType.getValue(network.getBroadcastUri()), nicVO.getUuid(), context.getDomain().getName() + "-" + + context.getAccount().getAccountName(), nic.getName()); + final CreateLogicalSwitchPortAnswer answer = (CreateLogicalSwitchPortAnswer)agentMgr.easySend(niciraNvpHost.getId(), cmd); + + if (answer == null || !answer.getResult()) { + throw new NicPreparationException("CreateLogicalSwitchPortCommand failed"); + } + + final NiciraNvpNicMappingVO nicMap = + new NiciraNvpNicMappingVO(BroadcastDomainType.getValue(network.getBroadcastUri()), answer.getLogicalSwitchPortUuid(), nicVO.getUuid()); + niciraNvpNicMappingDao.persist(nicMap); + + return true; + } + + @Override + public boolean release(Network network, NicProfile nic, VirtualMachineProfile vm, ReservationContext context) throws ConcurrentOperationException, + ResourceUnavailableException { + + if (!canHandle(network, Service.Connectivity)) { + return false; + } + + if (network.getBroadcastUri() == null) { + s_logger.error("Nic has no broadcast Uri with the LSwitch Uuid"); + return false; + } + + final NicVO nicVO = nicDao.findById(nic.getId()); + + final List devices = niciraNvpDao.listByPhysicalNetwork(network.getPhysicalNetworkId()); + if (devices.isEmpty()) { + s_logger.error("No NiciraNvp Controller on physical network " + network.getPhysicalNetworkId()); + return false; + } + final NiciraNvpDeviceVO niciraNvpDevice = devices.get(0); + final HostVO niciraNvpHost = hostDao.findById(niciraNvpDevice.getHostId()); + + final NiciraNvpNicMappingVO nicMap = niciraNvpNicMappingDao.findByNicUuid(nicVO.getUuid()); + if (nicMap == null) { + s_logger.error("No mapping for nic " + nic.getName()); + return false; + } + + final DeleteLogicalSwitchPortCommand cmd = new DeleteLogicalSwitchPortCommand(nicMap.getLogicalSwitchUuid(), nicMap.getLogicalSwitchPortUuid()); + final DeleteLogicalSwitchPortAnswer answer = (DeleteLogicalSwitchPortAnswer)agentMgr.easySend(niciraNvpHost.getId(), cmd); + + if (answer == null || !answer.getResult()) { + s_logger.error("DeleteLogicalSwitchPortCommand failed"); + return false; + } + + niciraNvpNicMappingDao.remove(nicMap.getId()); + + return true; + } + + @Override + public boolean shutdown(Network network, ReservationContext context, boolean cleanup) throws ConcurrentOperationException, ResourceUnavailableException { + if (!canHandle(network, Service.Connectivity)) { + return false; + } + + final List devices = niciraNvpDao.listByPhysicalNetwork(network.getPhysicalNetworkId()); + if (devices.isEmpty()) { + s_logger.error("No NiciraNvp Controller on physical network " + network.getPhysicalNetworkId()); + return false; + } + final NiciraNvpDeviceVO niciraNvpDevice = devices.get(0); + final HostVO niciraNvpHost = hostDao.findById(niciraNvpDevice.getHostId()); + + if (networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.SourceNat, Provider.NiciraNvp)) { + s_logger.debug("Apparently we were providing SourceNat on this network"); + + // Deleting the LogicalRouter will also take care of all provisioned + // nat rules. + final NiciraNvpRouterMappingVO routermapping = niciraNvpRouterMappingDao.findByNetworkId(network.getId()); + if (routermapping == null) { + s_logger.warn("No logical router uuid found for network " + network.getDisplayText()); + // This might be cause by a failed deployment, so don't make shutdown fail as well. + return true; + } + + final DeleteLogicalRouterCommand cmd = new DeleteLogicalRouterCommand(routermapping.getLogicalRouterUuid()); + final DeleteLogicalRouterAnswer answer = (DeleteLogicalRouterAnswer)agentMgr.easySend(niciraNvpHost.getId(), cmd); + if (answer.getResult() == false) { + s_logger.error("Failed to delete LogicalRouter for network " + network.getDisplayText()); + return false; + } + + niciraNvpRouterMappingDao.remove(routermapping.getId()); + } + + return true; + } + + @Override + public boolean destroy(Network network, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException { + if (!canHandle(network, Service.Connectivity)) { + return false; + } + + return true; + } + + @Override + public boolean isReady(PhysicalNetworkServiceProvider provider) { + return true; + } + + @Override + public boolean shutdownProviderInstances(PhysicalNetworkServiceProvider provider, ReservationContext context) throws ConcurrentOperationException, + ResourceUnavailableException { + // Nothing to do here. + return true; + } + + @Override + public boolean canEnableIndividualServices() { + return true; + } + + @Override + public boolean verifyServicesCombination(Set services) { + // This element can only function in a Nicra Nvp based + // SDN network, so Connectivity needs to be present here + if (!services.contains(Service.Connectivity)) { + s_logger.warn("Unable to provide services without Connectivity service enabled for this element"); + return false; + } + if ((services.contains(Service.PortForwarding) || services.contains(Service.StaticNat)) && !services.contains(Service.SourceNat)) { + s_logger.warn("Unable to provide StaticNat and/or PortForwarding without the SourceNat service"); + return false; + } + return true; + } + + private static Map> setCapabilities() { + final Map> capabilities = new HashMap>(); + + // L2 Support : SDN provisioning + capabilities.put(Service.Connectivity, null); + + // L3 Support : Generic? + capabilities.put(Service.Gateway, null); + + // L3 Support : SourceNat + final Map sourceNatCapabilities = new HashMap(); + sourceNatCapabilities.put(Capability.SupportedSourceNatTypes, "peraccount"); + sourceNatCapabilities.put(Capability.RedundantRouter, "false"); + capabilities.put(Service.SourceNat, sourceNatCapabilities); + + // L3 Support : Port Forwarding + capabilities.put(Service.PortForwarding, null); + + // L3 support : StaticNat + capabilities.put(Service.StaticNat, null); + + return capabilities; + } + + @Override + public List> getCommands() { + final List> cmdList = new ArrayList>(); + cmdList.add(AddNiciraNvpDeviceCmd.class); + cmdList.add(DeleteNiciraNvpDeviceCmd.class); + cmdList.add(ListNiciraNvpDeviceNetworksCmd.class); + cmdList.add(ListNiciraNvpDevicesCmd.class); + return cmdList; + } + + @Override + @DB + public NiciraNvpDeviceVO addNiciraNvpDevice(AddNiciraNvpDeviceCmd cmd) { + final ServerResource resource = new NiciraNvpResource(); + final String deviceName = Network.Provider.NiciraNvp.getName(); + final NetworkDevice networkDevice = NetworkDevice.getNetworkDevice(deviceName); + if (networkDevice == null) { + throw new CloudRuntimeException("No network device found for " + deviceName); + } + final Long physicalNetworkId = cmd.getPhysicalNetworkId(); + final PhysicalNetworkVO physicalNetwork = physicalNetworkDao.findById(physicalNetworkId); + if (physicalNetwork == null) { + throw new InvalidParameterValueException("Could not find phyical network with ID: " + physicalNetworkId); + } + final long zoneId = physicalNetwork.getDataCenterId(); + + final PhysicalNetworkServiceProviderVO ntwkSvcProvider = + physicalNetworkServiceProviderDao.findByServiceProvider(physicalNetwork.getId(), networkDevice.getNetworkServiceProvder()); + if (ntwkSvcProvider == null) { + throw new CloudRuntimeException("Network Service Provider: " + networkDevice.getNetworkServiceProvder() + " is not enabled in the physical network: " + + physicalNetworkId + "to add this device"); + } else if (ntwkSvcProvider.getState() == PhysicalNetworkServiceProvider.State.Shutdown) { + throw new CloudRuntimeException("Network Service Provider: " + ntwkSvcProvider.getProviderName() + " is in shutdown state in the physical network: " + + physicalNetworkId + "to add this device"); + } + + if (niciraNvpDao.listByPhysicalNetwork(physicalNetworkId).size() != 0) { + throw new CloudRuntimeException("A NiciraNvp device is already configured on this physical network"); + } + + final Map params = new HashMap(); + params.put("guid", UUID.randomUUID().toString()); + params.put("zoneId", String.valueOf(physicalNetwork.getDataCenterId())); + params.put("physicalNetworkId", String.valueOf(physicalNetwork.getId())); + params.put("name", "Nicira Controller - " + cmd.getHost()); + params.put("ip", cmd.getHost()); + params.put("adminuser", cmd.getUsername()); + params.put("adminpass", cmd.getPassword()); + params.put("transportzoneuuid", cmd.getTransportzoneUuid()); + // FIXME What to do with multiple isolation types + params.put("transportzoneisotype", physicalNetwork.getIsolationMethods().get(0).toLowerCase()); + if (cmd.getL3GatewayServiceUuid() != null) { + params.put("l3gatewayserviceuuid", cmd.getL3GatewayServiceUuid()); + } + + final Map hostdetails = new HashMap(); + hostdetails.putAll(params); + + try { + resource.configure(cmd.getHost(), hostdetails); + + final Host host = resourceMgr.addHost(zoneId, resource, Host.Type.L2Networking, params); + if (host != null) { + return Transaction.execute(new TransactionCallback() { + @Override + public NiciraNvpDeviceVO doInTransaction(TransactionStatus status) { + final NiciraNvpDeviceVO niciraNvpDevice = new NiciraNvpDeviceVO(host.getId(), physicalNetworkId, ntwkSvcProvider.getProviderName(), deviceName); + niciraNvpDao.persist(niciraNvpDevice); + + final DetailVO detail = new DetailVO(host.getId(), "niciranvpdeviceid", String.valueOf(niciraNvpDevice.getId())); + hostDetailsDao.persist(detail); + + return niciraNvpDevice; + } + }); + } else { + throw new CloudRuntimeException("Failed to add Nicira Nvp Device due to internal error."); + } + } catch (final ConfigurationException e) { + throw new CloudRuntimeException(e.getMessage()); + } + } + + @Override + public NiciraNvpDeviceResponse createNiciraNvpDeviceResponse(NiciraNvpDeviceVO niciraNvpDeviceVO) { + final HostVO niciraNvpHost = hostDao.findById(niciraNvpDeviceVO.getHostId()); + hostDao.loadDetails(niciraNvpHost); + + final NiciraNvpDeviceResponse response = new NiciraNvpDeviceResponse(); + response.setDeviceName(niciraNvpDeviceVO.getDeviceName()); + final PhysicalNetwork pnw = ApiDBUtils.findPhysicalNetworkById(niciraNvpDeviceVO.getPhysicalNetworkId()); + if (pnw != null) { + response.setPhysicalNetworkId(pnw.getUuid()); + } + response.setId(niciraNvpDeviceVO.getUuid()); + response.setProviderName(niciraNvpDeviceVO.getProviderName()); + response.setHostName(niciraNvpHost.getDetail("ip")); + response.setTransportZoneUuid(niciraNvpHost.getDetail("transportzoneuuid")); + response.setL3GatewayServiceUuid(niciraNvpHost.getDetail("l3gatewayserviceuuid")); + response.setObjectName("niciranvpdevice"); + return response; + } + + @Override + public boolean deleteNiciraNvpDevice(DeleteNiciraNvpDeviceCmd cmd) { + final Long niciraDeviceId = cmd.getNiciraNvpDeviceId(); + final NiciraNvpDeviceVO niciraNvpDevice = niciraNvpDao.findById(niciraDeviceId); + if (niciraNvpDevice == null) { + throw new InvalidParameterValueException("Could not find a nicira device with id " + niciraDeviceId); + } + + // Find the physical network we work for + final Long physicalNetworkId = niciraNvpDevice.getPhysicalNetworkId(); + final PhysicalNetworkVO physicalNetwork = physicalNetworkDao.findById(physicalNetworkId); + if (physicalNetwork != null) { + // Lets see if there are networks that use us + // Find the nicira networks on this physical network + final List networkList = networkDao.listByPhysicalNetwork(physicalNetworkId); + if (networkList != null) { + // Networks with broadcast type lswitch are ours + for (final NetworkVO network : networkList) { + if (network.getBroadcastDomainType() == Networks.BroadcastDomainType.Lswitch) { + if (network.getState() != Network.State.Shutdown && network.getState() != Network.State.Destroy) { + throw new CloudRuntimeException("This Nicira Nvp device can not be deleted as there are one or more logical networks provisioned by cloudstack."); + } + } + } + } + } + + final HostVO niciraHost = hostDao.findById(niciraNvpDevice.getHostId()); + final Long hostId = niciraHost.getId(); + + niciraHost.setResourceState(ResourceState.Maintenance); + hostDao.update(hostId, niciraHost); + resourceMgr.deleteHost(hostId, false, false); + + niciraNvpDao.remove(niciraDeviceId); + return true; + } + + @Override + public List listNiciraNvpDevices(ListNiciraNvpDevicesCmd cmd) { + final Long physicalNetworkId = cmd.getPhysicalNetworkId(); + final Long niciraNvpDeviceId = cmd.getNiciraNvpDeviceId(); + List responseList = new ArrayList(); + + if (physicalNetworkId == null && niciraNvpDeviceId == null) { + throw new InvalidParameterValueException("Either physical network Id or nicira device Id must be specified"); + } + + if (niciraNvpDeviceId != null) { + final NiciraNvpDeviceVO niciraNvpDevice = niciraNvpDao.findById(niciraNvpDeviceId); + if (niciraNvpDevice == null) { + throw new InvalidParameterValueException("Could not find Nicira Nvp device with id: " + niciraNvpDevice); + } + responseList.add(niciraNvpDevice); + } else { + final PhysicalNetworkVO physicalNetwork = physicalNetworkDao.findById(physicalNetworkId); + if (physicalNetwork == null) { + throw new InvalidParameterValueException("Could not find a physical network with id: " + physicalNetworkId); + } + responseList = niciraNvpDao.listByPhysicalNetwork(physicalNetworkId); + } + + return responseList; + } + + @Override + public List listNiciraNvpDeviceNetworks(ListNiciraNvpDeviceNetworksCmd cmd) { + final Long niciraDeviceId = cmd.getNiciraNvpDeviceId(); + final NiciraNvpDeviceVO niciraNvpDevice = niciraNvpDao.findById(niciraDeviceId); + if (niciraNvpDevice == null) { + throw new InvalidParameterValueException("Could not find a nicira device with id " + niciraDeviceId); + } + + // Find the physical network we work for + final Long physicalNetworkId = niciraNvpDevice.getPhysicalNetworkId(); + final PhysicalNetworkVO physicalNetwork = physicalNetworkDao.findById(physicalNetworkId); + if (physicalNetwork == null) { + // No such physical network, so no provisioned networks + return Collections.emptyList(); + } + + // Find the nicira networks on this physical network + final List networkList = networkDao.listByPhysicalNetwork(physicalNetworkId); + if (networkList == null) { + return Collections.emptyList(); + } + + // Networks with broadcast type lswitch are ours + final List responseList = new ArrayList(); + for (final NetworkVO network : networkList) { + if (network.getBroadcastDomainType() == Networks.BroadcastDomainType.Lswitch) { + responseList.add(network); + } + } + + return responseList; + } + + @Override + public HostVO createHostVOForConnectedAgent(HostVO host, StartupCommand[] cmd) { + // TODO Auto-generated method stub + return null; + } + + @Override + public HostVO createHostVOForDirectConnectAgent(HostVO host, StartupCommand[] startup, ServerResource resource, Map details, List hostTags) { + if (!(startup[0] instanceof StartupNiciraNvpCommand)) { + return null; + } + host.setType(Host.Type.L2Networking); + return host; + } + + @Override + public DeleteHostAnswer deleteHost(HostVO host, boolean isForced, boolean isForceDeleteStorage) throws UnableDeleteHostException { + if (!(host.getType() == Host.Type.L2Networking)) { + return null; + } + return new DeleteHostAnswer(true); + } + + /** + * From interface SourceNatServiceProvider + */ + @Override + public IpDeployer getIpDeployer(Network network) { + return this; + } + + /** + * From interface IpDeployer + * + * @param network + * @param ipAddress + * @param services + * @return + * @throws ResourceUnavailableException + */ + @Override + public boolean applyIps(Network network, List ipAddress, Set services) throws ResourceUnavailableException { + if (services.contains(Service.SourceNat)) { + // Only if we need to provide SourceNat we need to configure the logical router + // SourceNat is required for StaticNat and PortForwarding + final List devices = niciraNvpDao.listByPhysicalNetwork(network.getPhysicalNetworkId()); + if (devices.isEmpty()) { + s_logger.error("No NiciraNvp Controller on physical network " + network.getPhysicalNetworkId()); + return false; + } + final NiciraNvpDeviceVO niciraNvpDevice = devices.get(0); + final HostVO niciraNvpHost = hostDao.findById(niciraNvpDevice.getHostId()); + hostDao.loadDetails(niciraNvpHost); + + final NiciraNvpRouterMappingVO routermapping = niciraNvpRouterMappingDao.findByNetworkId(network.getId()); + if (routermapping == null) { + s_logger.error("No logical router uuid found for network " + network.getDisplayText()); + return false; + } + + final List cidrs = new ArrayList(); + for (final PublicIpAddress ip : ipAddress) { + if (ip.getState() == IpAddress.State.Releasing) { + // If we are releasing we don't need to push this ip to + // the Logical Router + continue; + } + cidrs.add(ip.getAddress().addr() + "/" + NetUtils.getCidrSize(ip.getNetmask())); + } + final ConfigurePublicIpsOnLogicalRouterCommand cmd = + new ConfigurePublicIpsOnLogicalRouterCommand(routermapping.getLogicalRouterUuid(), niciraNvpHost.getDetail("l3gatewayserviceuuid"), cidrs); + final ConfigurePublicIpsOnLogicalRouterAnswer answer = (ConfigurePublicIpsOnLogicalRouterAnswer)agentMgr.easySend(niciraNvpHost.getId(), cmd); + //FIXME answer can be null if the host is down + return answer.getResult(); + } else { + s_logger.debug("No need to provision ip addresses as we are not providing L3 services."); + } + + return true; + } + + /** + * From interface StaticNatServiceProvider + */ + @Override + public boolean applyStaticNats(Network network, List rules) throws ResourceUnavailableException { + if (!canHandle(network, Service.StaticNat)) { + return false; + } + + final List devices = niciraNvpDao.listByPhysicalNetwork(network.getPhysicalNetworkId()); + if (devices.isEmpty()) { + s_logger.error("No NiciraNvp Controller on physical network " + network.getPhysicalNetworkId()); + return false; + } + final NiciraNvpDeviceVO niciraNvpDevice = devices.get(0); + final HostVO niciraNvpHost = hostDao.findById(niciraNvpDevice.getHostId()); + + final NiciraNvpRouterMappingVO routermapping = niciraNvpRouterMappingDao.findByNetworkId(network.getId()); + if (routermapping == null) { + s_logger.error("No logical router uuid found for network " + network.getDisplayText()); + return false; + } + + final List staticNatRules = new ArrayList(); + for (final StaticNat rule : rules) { + final IpAddress sourceIp = networkModel.getIp(rule.getSourceIpAddressId()); + // Force the nat rule into the StaticNatRuleTO, no use making a new TO object + // we only need the source and destination ip. Unfortunately no mention if a rule + // is new. + final StaticNatRuleTO ruleTO = + new StaticNatRuleTO(1, sourceIp.getAddress().addr(), MIN_PORT, MAX_PORT, rule.getDestIpAddress(), MIN_PORT, MAX_PORT, "any", rule.isForRevoke(), false); + staticNatRules.add(ruleTO); + } + + final ConfigureStaticNatRulesOnLogicalRouterCommand cmd = new ConfigureStaticNatRulesOnLogicalRouterCommand(routermapping.getLogicalRouterUuid(), staticNatRules); + final ConfigureStaticNatRulesOnLogicalRouterAnswer answer = (ConfigureStaticNatRulesOnLogicalRouterAnswer)agentMgr.easySend(niciraNvpHost.getId(), cmd); + + return answer.getResult(); + } + + /** + * From interface PortForwardingServiceProvider + */ + @Override + public boolean applyPFRules(Network network, List rules) throws ResourceUnavailableException { + if (!canHandle(network, Service.PortForwarding)) { + return false; + } + + final List devices = niciraNvpDao.listByPhysicalNetwork(network.getPhysicalNetworkId()); + if (devices.isEmpty()) { + s_logger.error("No NiciraNvp Controller on physical network " + network.getPhysicalNetworkId()); + return false; + } + final NiciraNvpDeviceVO niciraNvpDevice = devices.get(0); + final HostVO niciraNvpHost = hostDao.findById(niciraNvpDevice.getHostId()); + + final NiciraNvpRouterMappingVO routermapping = niciraNvpRouterMappingDao.findByNetworkId(network.getId()); + if (routermapping == null) { + s_logger.error("No logical router uuid found for network " + network.getDisplayText()); + return false; + } + + final List portForwardingRules = new ArrayList(); + for (final PortForwardingRule rule : rules) { + final IpAddress sourceIp = networkModel.getIp(rule.getSourceIpAddressId()); + final Vlan vlan = vlanDao.findById(sourceIp.getVlanId()); + final PortForwardingRuleTO ruleTO = new PortForwardingRuleTO(rule, vlan.getVlanTag(), sourceIp.getAddress().addr()); + portForwardingRules.add(ruleTO); + } + + final ConfigurePortForwardingRulesOnLogicalRouterCommand cmd = + new ConfigurePortForwardingRulesOnLogicalRouterCommand(routermapping.getLogicalRouterUuid(), portForwardingRules); + final ConfigurePortForwardingRulesOnLogicalRouterAnswer answer = (ConfigurePortForwardingRulesOnLogicalRouterAnswer)agentMgr.easySend(niciraNvpHost.getId(), cmd); + + return answer.getResult(); + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/element/NiciraNvpElementService.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/element/NiciraNvpElementService.java new file mode 100644 index 0000000000..dae1a0f280 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/element/NiciraNvpElementService.java @@ -0,0 +1,45 @@ +// +// 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.network.element; + +import java.util.List; + +import com.cloud.api.commands.AddNiciraNvpDeviceCmd; +import com.cloud.api.commands.DeleteNiciraNvpDeviceCmd; +import com.cloud.api.commands.ListNiciraNvpDeviceNetworksCmd; +import com.cloud.api.commands.ListNiciraNvpDevicesCmd; +import com.cloud.api.response.NiciraNvpDeviceResponse; +import com.cloud.network.Network; +import com.cloud.network.NiciraNvpDeviceVO; +import com.cloud.utils.component.PluggableService; + +public interface NiciraNvpElementService extends PluggableService { + + public NiciraNvpDeviceVO addNiciraNvpDevice(AddNiciraNvpDeviceCmd cmd); + + public NiciraNvpDeviceResponse createNiciraNvpDeviceResponse(NiciraNvpDeviceVO niciraDeviceVO); + + boolean deleteNiciraNvpDevice(DeleteNiciraNvpDeviceCmd cmd); + + List listNiciraNvpDeviceNetworks(ListNiciraNvpDeviceNetworksCmd cmd); + + List listNiciraNvpDevices(ListNiciraNvpDevicesCmd cmd); + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/guru/NiciraNvpGuestNetworkGuru.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/guru/NiciraNvpGuestNetworkGuru.java new file mode 100644 index 0000000000..53cacad293 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/guru/NiciraNvpGuestNetworkGuru.java @@ -0,0 +1,277 @@ +// +// 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.network.guru; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; + +import javax.inject.Inject; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.CreateLogicalSwitchAnswer; +import com.cloud.agent.api.CreateLogicalSwitchCommand; +import com.cloud.agent.api.DeleteLogicalSwitchAnswer; +import com.cloud.agent.api.DeleteLogicalSwitchCommand; +import com.cloud.agent.api.FindLogicalSwitchCommand; +import com.cloud.dc.DataCenter; +import com.cloud.dc.DataCenter.NetworkType; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.deploy.DeployDestination; +import com.cloud.deploy.DeploymentPlan; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.InsufficientAddressCapacityException; +import com.cloud.exception.InsufficientVirtualNetworkCapacityException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.host.dao.HostDetailsDao; +import com.cloud.network.Network; +import com.cloud.network.Network.Service; +import com.cloud.network.Network.State; +import com.cloud.network.NetworkModel; +import com.cloud.network.NetworkProfile; +import com.cloud.network.Networks.BroadcastDomainType; +import com.cloud.network.NiciraNvpDeviceVO; +import com.cloud.network.PhysicalNetwork; +import com.cloud.network.PhysicalNetwork.IsolationMethod; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.dao.NiciraNvpDao; +import com.cloud.network.dao.PhysicalNetworkDao; +import com.cloud.network.dao.PhysicalNetworkVO; +import com.cloud.offering.NetworkOffering; +import com.cloud.offerings.dao.NetworkOfferingServiceMapDao; +import com.cloud.resource.ResourceManager; +import com.cloud.user.Account; +import com.cloud.user.dao.AccountDao; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.NicProfile; +import com.cloud.vm.ReservationContext; +import com.cloud.vm.VirtualMachineProfile; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class NiciraNvpGuestNetworkGuru extends GuestNetworkGuru { + private static final int MAX_NAME_LENGTH = 40; + + private static final Logger s_logger = LoggerFactory.getLogger(NiciraNvpGuestNetworkGuru.class); + + @Inject + protected NetworkModel networkModel; + @Inject + protected NetworkDao networkDao; + @Inject + protected DataCenterDao zoneDao; + @Inject + protected PhysicalNetworkDao physicalNetworkDao; + @Inject + protected AccountDao accountDao; + @Inject + protected NiciraNvpDao niciraNvpDao; + @Inject + protected HostDao hostDao; + @Inject + protected ResourceManager resourceMgr; + @Inject + protected AgentManager agentMgr; + @Inject + protected HostDetailsDao hostDetailsDao; + @Inject + protected NetworkOfferingServiceMapDao ntwkOfferingSrvcDao; + + public NiciraNvpGuestNetworkGuru() { + super(); + _isolationMethods = new IsolationMethod[] { IsolationMethod.STT, IsolationMethod.VXLAN }; + } + + @Override + protected boolean canHandle(final NetworkOffering offering, final NetworkType networkType, final PhysicalNetwork physicalNetwork) { + // This guru handles only Guest Isolated network that supports Source nat service + if (networkType == NetworkType.Advanced && isMyTrafficType(offering.getTrafficType()) && offering.getGuestType() == Network.GuestType.Isolated + && isMyIsolationMethod(physicalNetwork) && ntwkOfferingSrvcDao.areServicesSupportedByNetworkOffering(offering.getId(), Service.Connectivity)) { + return true; + } else { + s_logger.debug("Cannot handle rquest. See GuestNetworkGuru message to check isolation methods. Details I have:\nNetwork type = " + networkType + "\nTraffic type = " + offering.getTrafficType() + "\nGuest type = " + offering.getGuestType()); + return false; + } + } + + @Override + public Network design(final NetworkOffering offering, final DeploymentPlan plan, final Network userSpecified, final Account owner) { + // Check if the isolation type of the related physical network is supported + final PhysicalNetworkVO physnet = physicalNetworkDao.findById(plan.getPhysicalNetworkId()); + final DataCenter dc = _dcDao.findById(plan.getDataCenterId()); + if (!canHandle(offering, dc.getNetworkType(), physnet)) { + s_logger.debug("Refusing to design this network"); + return null; + } + + final List devices = niciraNvpDao.listByPhysicalNetwork(physnet.getId()); + if (devices.isEmpty()) { + s_logger.error("No NiciraNvp Controller on physical network " + physnet.getName()); + return null; + } + final NiciraNvpDeviceVO niciraNvpDeviceVO = devices.get(0); + s_logger.debug("Nicira Nvp " + niciraNvpDeviceVO.getUuid() + " found on physical network " + physnet.getId()); + + checkThatLogicalSwitchExists(userSpecified, niciraNvpDeviceVO); + + s_logger.debug("Physical isolation type is supported, asking GuestNetworkGuru to design this network"); + final NetworkVO networkObject = (NetworkVO) super.design(offering, plan, userSpecified, owner); + if (networkObject == null) { + return null; + } + networkObject.setBroadcastDomainType(BroadcastDomainType.Lswitch); + + return networkObject; + } + + private void checkThatLogicalSwitchExists(final Network userSpecified, final NiciraNvpDeviceVO niciraNvpDeviceVO) { + final URI broadcastUri = userSpecified.getBroadcastUri(); + if(broadcastUri != null) { + final String lswitchUuid = broadcastUri.getRawSchemeSpecificPart(); + if(!lswitchExists(lswitchUuid, niciraNvpDeviceVO)) { + throw new CloudRuntimeException("Refusing to design this network because the specified lswitch (" + lswitchUuid + ") does not exist."); + } + } + } + + private boolean lswitchExists(String lswitchUuid, NiciraNvpDeviceVO niciraNvpDeviceVO) { + try { + final Answer answer = agentMgr.send(niciraNvpDeviceVO.getHostId(), new FindLogicalSwitchCommand(lswitchUuid)); + return answer.getResult(); + } catch (AgentUnavailableException | OperationTimedoutException e) { + s_logger.warn("There was an error while trying to find logical switch " + lswitchUuid); + return false; + } + } + + @Override + public Network implement(final Network network, final NetworkOffering offering, final DeployDestination dest, final ReservationContext context) + throws InsufficientVirtualNetworkCapacityException { + assert network.getState() == State.Implementing : "Why are we implementing " + network; + + final long dcId = dest.getDataCenter().getId(); + + Long physicalNetworkId = network.getPhysicalNetworkId(); + + // physical network id can be null in Guest Network in Basic zone, so locate the physical network + if (physicalNetworkId == null) { + physicalNetworkId = networkModel.findPhysicalNetworkId(dcId, offering.getTags(), offering.getTrafficType()); + } + + final NetworkVO implemented = new NetworkVO(network.getTrafficType(), network.getMode(), network.getBroadcastDomainType(), network.getNetworkOfferingId(), + State.Allocated, network.getDataCenterId(), physicalNetworkId, offering.getRedundantRouter()); + + if (network.getGateway() != null) { + implemented.setGateway(network.getGateway()); + } + + if (network.getCidr() != null) { + implemented.setCidr(network.getCidr()); + } + + // Name is either the given name or the uuid + String name = network.getName(); + if (name == null || name.isEmpty()) { + name = ((NetworkVO) network).getUuid(); + } + if (name.length() > MAX_NAME_LENGTH) { + name = name.substring(0, MAX_NAME_LENGTH - 1); + } + + final List devices = niciraNvpDao.listByPhysicalNetwork(physicalNetworkId); + if (devices.isEmpty()) { + s_logger.error("No NiciraNvp Controller on physical network " + physicalNetworkId); + return null; + } + final NiciraNvpDeviceVO niciraNvpDevice = devices.get(0); + final HostVO niciraNvpHost = hostDao.findById(niciraNvpDevice.getHostId()); + hostDao.loadDetails(niciraNvpHost); + final String transportzoneuuid = niciraNvpHost.getDetail("transportzoneuuid"); + final String transportzoneisotype = niciraNvpHost.getDetail("transportzoneisotype"); + + final CreateLogicalSwitchCommand cmd = new CreateLogicalSwitchCommand(transportzoneuuid, transportzoneisotype, name, context.getDomain().getName() + "-" + + context.getAccount().getAccountName()); + final CreateLogicalSwitchAnswer answer = (CreateLogicalSwitchAnswer) agentMgr.easySend(niciraNvpHost.getId(), cmd); + + if (answer == null || !answer.getResult()) { + s_logger.error("CreateLogicalSwitchCommand failed"); + return null; + } + + try { + implemented.setBroadcastUri(new URI("lswitch", answer.getLogicalSwitchUuid(), null)); + implemented.setBroadcastDomainType(BroadcastDomainType.Lswitch); + s_logger.info("Implemented OK, network linked to = " + implemented.getBroadcastUri().toString()); + } catch (final URISyntaxException e) { + s_logger.error("Unable to store logical switch id in broadcast uri, uuid = " + implemented.getUuid(), e); + return null; + } + + return implemented; + } + + @Override + public void reserve(final NicProfile nic, final Network network, final VirtualMachineProfile vm, final DeployDestination dest, final ReservationContext context) + throws InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException { + super.reserve(nic, network, vm, dest, context); + } + + @Override + public boolean release(final NicProfile nic, final VirtualMachineProfile vm, final String reservationId) { + return super.release(nic, vm, reservationId); + } + + @Override + public void shutdown(final NetworkProfile profile, final NetworkOffering offering) { + final NetworkVO networkObject = networkDao.findById(profile.getId()); + if (networkObject.getBroadcastDomainType() != BroadcastDomainType.Lswitch || networkObject.getBroadcastUri() == null) { + s_logger.warn("BroadcastUri is empty or incorrect for guestnetwork " + networkObject.getDisplayText()); + return; + } + + final List devices = niciraNvpDao.listByPhysicalNetwork(networkObject.getPhysicalNetworkId()); + if (devices.isEmpty()) { + s_logger.error("No NiciraNvp Controller on physical network " + networkObject.getPhysicalNetworkId()); + return; + } + final NiciraNvpDeviceVO niciraNvpDevice = devices.get(0); + final HostVO niciraNvpHost = hostDao.findById(niciraNvpDevice.getHostId()); + + final DeleteLogicalSwitchCommand cmd = new DeleteLogicalSwitchCommand(BroadcastDomainType.getValue(networkObject.getBroadcastUri())); + final DeleteLogicalSwitchAnswer answer = (DeleteLogicalSwitchAnswer) agentMgr.easySend(niciraNvpHost.getId(), cmd); + + if (answer == null || !answer.getResult()) { + s_logger.error("DeleteLogicalSwitchCommand failed"); + } + + super.shutdown(profile, offering); + } + + @Override + public boolean trash(final Network network, final NetworkOffering offering) { + return super.trash(network, offering); + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/AccessConfiguration.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/AccessConfiguration.java new file mode 100644 index 0000000000..b5291dc362 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/AccessConfiguration.java @@ -0,0 +1,44 @@ +// +// 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.network.nicira; + +import java.util.List; + +public abstract class AccessConfiguration extends BaseNiciraNamedEntity { + + protected List logicalPortEgressRules; + protected List logicalPortIngressRules; + + public List getLogicalPortEgressRules() { + return logicalPortEgressRules; + } + + public void setLogicalPortEgressRules(final List logicalPortEgressRules) { + this.logicalPortEgressRules = logicalPortEgressRules; + } + + public List getLogicalPortIngressRules() { + return logicalPortIngressRules; + } + + public void setLogicalPortIngressRules(final List logicalPortIngressRules) { + this.logicalPortIngressRules = logicalPortIngressRules; + } +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/AccessRule.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/AccessRule.java new file mode 100644 index 0000000000..e614db6e43 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/AccessRule.java @@ -0,0 +1,58 @@ +// +// 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.network.nicira; + +import java.io.Serializable; + +import org.apache.commons.lang.builder.ReflectionToStringBuilder; +import org.apache.commons.lang.builder.ToStringStyle; + +@SuppressWarnings("serial") +public abstract class AccessRule implements Serializable { + + public static final String ETHERTYPE_IPV4 = "IPv4"; + public static final String ETHERTYPE_IPV6 = "IPv6"; + + protected String ethertype = ETHERTYPE_IPV4; + + protected int protocol; + + + public String getEthertype() { + return ethertype; + } + + public void setEthertype(String ethertype) { + this.ethertype = ethertype; + } + + public int getProtocol() { + return protocol; + } + + public void setProtocol(int protocol) { + this.protocol = protocol; + } + + @Override + public String toString() { + return ReflectionToStringBuilder.reflectionToString(this, ToStringStyle.DEFAULT_STYLE, false); + } +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/Acl.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/Acl.java new file mode 100644 index 0000000000..870124ed60 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/Acl.java @@ -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. +// + +package com.cloud.network.nicira; + +public class Acl extends AccessConfiguration { +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/AclRule.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/AclRule.java new file mode 100644 index 0000000000..8a4c05332c --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/AclRule.java @@ -0,0 +1,209 @@ +// +// 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.network.nicira; + +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; + +@SuppressWarnings("serial") +public class AclRule extends AccessRule { + + public static final String ETHERTYPE_ARP = "ARP"; + + /** + * @TODO Convert this String into Enum and check the JSON communication still works + */ + protected String action; + + protected String sourceIpPrefix; + + protected String destinationIpPrefix; + + protected String sourceMacAddress; + + protected String destinationMacAddress; + + protected Integer sourcePortRangeMin; + + protected Integer destinationPortRangeMin; + + protected Integer sourcePortRangeMax; + + protected Integer destinationPortRangeMax; + + protected Integer icmpProtocolCode; + + protected Integer icmpProtocolType; + + protected int order; + + + /** + * Default constructor + */ + public AclRule() { + } + + /** + * Fully parameterized constructor + */ + public AclRule(String ethertype, int protocol, String action, String sourceMacAddress, + String destinationMacAddress, String sourceIpPrefix, String destinationIpPrefix, + Integer sourcePortRangeMin, Integer sourcePortRangeMax, + Integer destinationPortRangeMin, Integer destinationPortRangeMax, + int order, Integer icmpProtocolCode, Integer icmpProtocolType) { + this.ethertype = ethertype; + this.protocol = protocol; + this.action = action; + this.sourceMacAddress = sourceMacAddress; + this.destinationMacAddress = destinationMacAddress; + this.sourceIpPrefix = sourceIpPrefix; + this.destinationIpPrefix = destinationIpPrefix; + this.sourcePortRangeMin = sourcePortRangeMin; + this.sourcePortRangeMax = sourcePortRangeMax; + this.destinationPortRangeMin = destinationPortRangeMin; + this.destinationPortRangeMax = destinationPortRangeMax; + this.order = order; + this.icmpProtocolCode = icmpProtocolCode; + this.icmpProtocolType = icmpProtocolType; + } + + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getSourceIpPrefix() { + return sourceIpPrefix; + } + + public void setSourceIpPrefix(String sourceIpPrefix) { + this.sourceIpPrefix = sourceIpPrefix; + } + + public String getDestinationIpPrefix() { + return destinationIpPrefix; + } + + public void setDestinationIpPrefix(String destinationIpPrefix) { + this.destinationIpPrefix = destinationIpPrefix; + } + + public String getSourceMacAddress() { + return sourceMacAddress; + } + + public void setSourceMacAddress(String sourceMacAddress) { + this.sourceMacAddress = sourceMacAddress; + } + + public String getDestinationMacAddress() { + return destinationMacAddress; + } + + public void setDestinationMacAddress(String destinationMacAddress) { + this.destinationMacAddress = destinationMacAddress; + } + + public Integer getSourcePortRangeMin() { + return sourcePortRangeMin; + } + + public void setSourcePortRangeMin(Integer sourcePortRangeMin) { + this.sourcePortRangeMin = sourcePortRangeMin; + } + + public Integer getDestinationPortRangeMin() { + return destinationPortRangeMin; + } + + public void setDestinationPortRangeMin(Integer destinationPortRangeMin) { + this.destinationPortRangeMin = destinationPortRangeMin; + } + + public Integer getSourcePortRangeMax() { + return sourcePortRangeMax; + } + + public void setSourcePortRangeMax(Integer sourcePortRangeMax) { + this.sourcePortRangeMax = sourcePortRangeMax; + } + + public Integer getDestinationPortRangeMax() { + return destinationPortRangeMax; + } + + public void setDestinationPortRangeMax(Integer destinationPortRangeMax) { + this.destinationPortRangeMax = destinationPortRangeMax; + } + + public Integer getIcmpProtocolCode() { + return icmpProtocolCode; + } + + public void setIcmpProtocolCode(Integer icmpProtocolCode) { + this.icmpProtocolCode = icmpProtocolCode; + } + + public Integer getIcmpProtocolType() { + return icmpProtocolType; + } + + public void setIcmpProtocolType(Integer icmpProtocolType) { + this.icmpProtocolType = icmpProtocolType; + } + + public int getOrder() { + return order; + } + + public void setOrder(int order) { + this.order = order; + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 31) + .append(ethertype).append(protocol) + .toHashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (!(obj instanceof AclRule)) { + return false; + } + AclRule another = (AclRule) obj; + return new EqualsBuilder() + .append(ethertype, another.ethertype) + .append(protocol, another.protocol) + .isEquals(); + } +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/Attachment.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/Attachment.java new file mode 100644 index 0000000000..90ff131ff8 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/Attachment.java @@ -0,0 +1,24 @@ +// +// 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.network.nicira; + +public abstract class Attachment { + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/BaseNiciraEntity.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/BaseNiciraEntity.java new file mode 100644 index 0000000000..afcd233395 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/BaseNiciraEntity.java @@ -0,0 +1,85 @@ +// +// 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.network.nicira; + +import java.io.Serializable; + +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; +import org.apache.commons.lang.builder.ReflectionToStringBuilder; +import org.apache.commons.lang.builder.ToStringStyle; + +public abstract class BaseNiciraEntity implements Serializable { + protected String href; + protected String schema; + protected String uuid; + + public String getUuid() { + return uuid; + } + + public void setUuid(final String uuid) { + this.uuid = uuid; + } + + public String getHref() { + return href; + } + + public void setHref(final String href) { + this.href = href; + } + + public String getSchema() { + return schema; + } + + public void setSchema(final String schema) { + this.schema = schema; + } + + @Override + public String toString() { + return ReflectionToStringBuilder.reflectionToString(this, ToStringStyle.DEFAULT_STYLE, false); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 31) + .append(this.getClass()) + .append(uuid) + .toHashCode(); + } + + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (!(this.getClass().isInstance(obj))) { + return false; + } + final BaseNiciraEntity another = (BaseNiciraEntity) obj; + return new EqualsBuilder().append(uuid, another.uuid).isEquals(); + } +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/BaseNiciraNamedEntity.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/BaseNiciraNamedEntity.java new file mode 100644 index 0000000000..0e21ddb74c --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/BaseNiciraNamedEntity.java @@ -0,0 +1,44 @@ +// +// 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.network.nicira; + +import java.util.List; + +public abstract class BaseNiciraNamedEntity extends BaseNiciraEntity { + + protected String displayName; + protected List tags; + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(final String displayName) { + this.displayName = displayName; + } + + public List getTags() { + return tags; + } + + public void setTags(final List tags) { + this.tags = tags; + } +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/ControlClusterStatus.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/ControlClusterStatus.java new file mode 100644 index 0000000000..1ed7039343 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/ControlClusterStatus.java @@ -0,0 +1,105 @@ +// +// 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.network.nicira; + +public class ControlClusterStatus { + private String clusterStatus; + private Stats nodeStats; + private Stats queueStats; + private Stats portStats; + private Stats routerportStats; + private Stats switchStats; + private Stats zoneStats; + private Stats routerStats; + private Stats securityProfileStats; + private ClusterRoleConfig[] configuredRoles; + + public ClusterRoleConfig[] getConfiguredRoles() { + return configuredRoles; + } + + public String getClusterStatus() { + return clusterStatus; + } + + public Stats getNodeStats() { + return nodeStats; + } + + public Stats getLqueueStats() { + return queueStats; + } + + public Stats getLportStats() { + return portStats; + } + + public Stats getLrouterportStats() { + return routerportStats; + } + + public Stats getLswitchStats() { + return switchStats; + } + + public Stats getZoneStats() { + return zoneStats; + } + + public Stats getLrouterStats() { + return routerStats; + } + + public Stats getSecurityProfileStats() { + return securityProfileStats; + } + + public class Stats { + private int errorStateCount; + private int registeredCount; + private int activeCount; + + public int getErrorStateCount() { + return errorStateCount; + } + + public int getRegisteredCount() { + return registeredCount; + } + + public int getActiveCount() { + return activeCount; + } + + } + + public class ClusterRoleConfig { + public String majorityVersion; + public String role; + + public String getMajorityVersion(){ + return majorityVersion; + } + + public String getRole(){ + return role; + } + } +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/DestinationNatRule.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/DestinationNatRule.java new file mode 100644 index 0000000000..a0d5afcaa8 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/DestinationNatRule.java @@ -0,0 +1,113 @@ +// +// 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.network.nicira; + +public class DestinationNatRule extends NatRule { + private String toDestinationIpAddress; + private Integer toDestinationPort; + + public DestinationNatRule() { + setType("DestinationNatRule"); + } + + public String getToDestinationIpAddress() { + return toDestinationIpAddress; + } + + public void setToDestinationIpAddress(final String toDestinationIpAddress) { + this.toDestinationIpAddress = toDestinationIpAddress; + } + + public Integer getToDestinationPort() { + return toDestinationPort; + } + + public void setToDestinationPort(final Integer toDestinationPort) { + this.toDestinationPort = toDestinationPort; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((toDestinationIpAddress == null) ? 0 : toDestinationIpAddress.hashCode()); + result = prime * result + ((toDestinationPort == null) ? 0 : toDestinationPort.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + DestinationNatRule other = (DestinationNatRule)obj; + if (toDestinationIpAddress == null) { + if (other.toDestinationIpAddress != null) { + return false; + } + } else if (!toDestinationIpAddress.equals(other.toDestinationIpAddress)) { + return false; + } + if (toDestinationPort == null) { + if (other.toDestinationPort != null) { + return false; + } + } else if (!toDestinationPort.equals(other.toDestinationPort)) { + return false; + } + return true; + } + + @Override + public boolean equalsIgnoreUuid(Object obj) { + if (this == obj) { + return true; + } + if (!super.equalsIgnoreUuid(obj)) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + DestinationNatRule other = (DestinationNatRule)obj; + if (toDestinationIpAddress == null) { + if (other.toDestinationIpAddress != null) { + return false; + } + } else if (!toDestinationIpAddress.equals(other.toDestinationIpAddress)) { + return false; + } + if (toDestinationPort == null) { + if (other.toDestinationPort != null) { + return false; + } + } else if (!toDestinationPort.equals(other.toDestinationPort)) { + return false; + } + return true; + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/ExecutionCounter.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/ExecutionCounter.java new file mode 100644 index 0000000000..7eedebbf1d --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/ExecutionCounter.java @@ -0,0 +1,54 @@ +// +// 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.network.nicira; + +public class ExecutionCounter { + + private final int executionLimit; + private final ThreadLocal executionCount; + + public ExecutionCounter(final int executionLimit) { + this.executionLimit = executionLimit; + executionCount = new ThreadLocal() { + @Override + protected Integer initialValue() { + return new Integer(0); + } + }; + } + + public ExecutionCounter resetExecutionCounter() { + executionCount.set(0); + return this; + } + + public boolean hasReachedExecutionLimit() { + return executionCount.get() >= executionLimit; + } + + public ExecutionCounter incrementExecutionCounter() { + executionCount.set(executionCount.get() + 1); + return this; + } + + public int getValue() { + return executionCount.get(); + } +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/L3GatewayAttachment.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/L3GatewayAttachment.java new file mode 100644 index 0000000000..a1d5ce4a99 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/L3GatewayAttachment.java @@ -0,0 +1,55 @@ +// +// 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.network.nicira; + +/** + * + */ +public class L3GatewayAttachment extends Attachment { + private String l3GatewayServiceUuid; + private final String type = "L3GatewayAttachment"; + private Long vlanId; + + public L3GatewayAttachment(String l3GatewayServiceUuid) { + this.l3GatewayServiceUuid = l3GatewayServiceUuid; + } + + public L3GatewayAttachment(final String l3GatewayServiceUuid, final long vlanId) { + this.l3GatewayServiceUuid = l3GatewayServiceUuid; + this.vlanId = vlanId; + } + + public String getL3GatewayServiceUuid() { + return l3GatewayServiceUuid; + } + + public void setL3GatewayServiceUuid(final String l3GatewayServiceUuid) { + this.l3GatewayServiceUuid = l3GatewayServiceUuid; + } + + public long getVlanId() { + return vlanId; + } + + public void setVlanId(long vlanId) { + this.vlanId = vlanId; + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/LogicalRouter.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/LogicalRouter.java new file mode 100644 index 0000000000..edc9351222 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/LogicalRouter.java @@ -0,0 +1,71 @@ +// +// 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.network.nicira; + + +/** + * + */ +public class LogicalRouter extends BaseNiciraNamedEntity { + public static final String REPLICATION_MODE_SERVICE = "service"; + public static final String REPLICATION_MODE_SOURCE = "source"; + + private final String type = "LogicalRouterConfig"; + private RoutingConfig routingConfig; + private boolean distributed; + private boolean natSynchronizationEnabled; + private String replicationMode; + + public String getType() { + return type; + } + + public RoutingConfig getRoutingConfig() { + return routingConfig; + } + + public void setRoutingConfig(final RoutingConfig routingConfig) { + this.routingConfig = routingConfig; + } + + public boolean isDistributed() { + return distributed; + } + + public void setDistributed(final boolean distributed) { + this.distributed = distributed; + } + + public boolean isNatSynchronizationEnabled() { + return natSynchronizationEnabled; + } + + public void setNatSynchronizationEnabled(final boolean natSynchronizationEnabled) { + this.natSynchronizationEnabled = natSynchronizationEnabled; + } + + public String getReplicationMode() { + return replicationMode; + } + + public void setReplicationMode(final String replicationMode) { + this.replicationMode = replicationMode; + } +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/LogicalRouterPort.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/LogicalRouterPort.java new file mode 100644 index 0000000000..d9ee09ad6f --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/LogicalRouterPort.java @@ -0,0 +1,62 @@ +// +// 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.network.nicira; + +import java.util.List; + +public class LogicalRouterPort extends BaseNiciraNamedEntity { + private Integer portno; + private boolean adminStatusEnabled; + private List ipAddresses; + private String macAddress; + private final String type = "LogicalRouterPortConfig"; + + public int getPortno() { + return portno; + } + + public void setPortno(final int portno) { + this.portno = portno; + } + + public boolean isAdminStatusEnabled() { + return adminStatusEnabled; + } + + public void setAdminStatusEnabled(final boolean adminStatusEnabled) { + this.adminStatusEnabled = adminStatusEnabled; + } + + public List getIpAddresses() { + return ipAddresses; + } + + public void setIpAddresses(final List ipAddresses) { + this.ipAddresses = ipAddresses; + } + + public String getMacAddress() { + return macAddress; + } + + public void setMacAddress(final String macAddress) { + this.macAddress = macAddress; + } +} \ No newline at end of file diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/LogicalSwitch.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/LogicalSwitch.java new file mode 100644 index 0000000000..b527d94107 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/LogicalSwitch.java @@ -0,0 +1,60 @@ +// +// 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.network.nicira; + +import java.util.List; + +public class LogicalSwitch extends BaseNiciraNamedEntity { + public static final String REPLICATION_MODE_SERVICE = "service"; + public static final String REPLICATION_MODE_SOURCE = "source"; + + private final String type = "LogicalSwitchConfig"; + private boolean portIsolationEnabled; + private List transportZones; + private String replicationMode; + + public boolean isPortIsolationEnabled() { + return portIsolationEnabled; + } + + public void setPortIsolationEnabled(final boolean portIsolationEnabled) { + this.portIsolationEnabled = portIsolationEnabled; + } + + public String getType() { + return type; + } + + public List getTransportZones() { + return transportZones; + } + + public void setTransportZones(final List transportZones) { + this.transportZones = transportZones; + } + + public String getReplicationMode() { + return replicationMode; + } + + public void setReplicationMode(final String replicationMode) { + this.replicationMode = replicationMode; + } +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/LogicalSwitchPort.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/LogicalSwitchPort.java new file mode 100644 index 0000000000..5e074235a7 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/LogicalSwitchPort.java @@ -0,0 +1,82 @@ +// +// 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.network.nicira; + +import java.util.List; + +public class LogicalSwitchPort extends BaseNiciraNamedEntity { + private Integer portno; + private boolean adminStatusEnabled; + private String queueUuid; + private List securityProfiles; + private List mirrorTargets; + private final String type = "LogicalSwitchPortConfig"; + + public LogicalSwitchPort() { + super(); + } + + public LogicalSwitchPort(final String displayName, final List tags, final boolean adminStatusEnabled) { + super(); + this.displayName = displayName; + this.tags = tags; + this.adminStatusEnabled = adminStatusEnabled; + } + + public Integer getPortno() { + return portno; + } + + public void setPortno(final Integer portno) { + this.portno = portno; + } + + public boolean isAdminStatusEnabled() { + return adminStatusEnabled; + } + + public void setAdminStatusEnabled(final boolean adminStatusEnabled) { + this.adminStatusEnabled = adminStatusEnabled; + } + + public String getQueueUuid() { + return queueUuid; + } + + public void setQueueUuid(final String queueUuid) { + this.queueUuid = queueUuid; + } + + public List getSecurityProfiles() { + return securityProfiles; + } + + public void setSecurityProfiles(final List securityProfiles) { + this.securityProfiles = securityProfiles; + } + + public List getMirrorTargets() { + return mirrorTargets; + } + + public void setMirrorTargets(final List mirrorTargets) { + this.mirrorTargets = mirrorTargets; + } +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/Match.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/Match.java new file mode 100644 index 0000000000..ae3a86c42e --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/Match.java @@ -0,0 +1,146 @@ +// +// 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.network.nicira; + +/** + * + */ +public class Match { + private Integer protocol; + private String sourceIpAddresses; + private String destinationIpAddresses; + private Integer sourcePort; + private Integer destinationPort; + private String ethertype = "IPv4"; + + public Integer getProtocol() { + return protocol; + } + + public void setProtocol(final Integer protocol) { + this.protocol = protocol; + } + + public Integer getSourcePort() { + return sourcePort; + } + + public void setSourcePort(final Integer sourcePort) { + this.sourcePort = sourcePort; + } + + public Integer getDestinationPort() { + return destinationPort; + } + + public void setDestinationPort(final Integer destinationPort) { + this.destinationPort = destinationPort; + } + + public String getEthertype() { + return ethertype; + } + + public void setEthertype(final String ethertype) { + this.ethertype = ethertype; + } + + public String getSourceIpAddresses() { + return sourceIpAddresses; + } + + public void setSourceIpAddresses(final String sourceIpAddresses) { + this.sourceIpAddresses = sourceIpAddresses; + } + + public String getDestinationIpAddresses() { + return destinationIpAddresses; + } + + public void setDestinationIpAddresses(final String destinationIpAddresses) { + this.destinationIpAddresses = destinationIpAddresses; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((destinationIpAddresses == null) ? 0 : destinationIpAddresses.hashCode()); + result = prime * result + ((destinationPort == null) ? 0 : destinationPort.hashCode()); + result = prime * result + ((ethertype == null) ? 0 : ethertype.hashCode()); + result = prime * result + ((protocol == null) ? 0 : protocol.hashCode()); + result = prime * result + ((sourceIpAddresses == null) ? 0 : sourceIpAddresses.hashCode()); + result = prime * result + ((sourcePort == null) ? 0 : sourcePort.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Match other = (Match)obj; + if (destinationIpAddresses == null) { + if (other.destinationIpAddresses != null) + return false; + } else if (!destinationIpAddresses.equals(other.destinationIpAddresses)) { + return false; + } + if (destinationPort == null) { + if (other.destinationPort != null) { + return false; + } + } else if (!destinationPort.equals(other.destinationPort)) { + return false; + } + if (ethertype == null) { + if (other.ethertype != null) + return false; + } else if (!ethertype.equals(other.ethertype)) { + return false; + } + if (protocol == null) { + if (other.protocol != null) + return false; + } else if (!protocol.equals(other.protocol)) { + return false; + } + if (sourceIpAddresses == null) { + if (other.sourceIpAddresses != null) + return false; + } else if (!sourceIpAddresses.equals(other.sourceIpAddresses)) { + return false; + } + if (sourcePort == null) { + if (other.sourcePort != null) + return false; + } else if (!sourcePort.equals(other.sourcePort)) { + return false; + } + return true; + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NatRule.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NatRule.java new file mode 100644 index 0000000000..912e352e27 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NatRule.java @@ -0,0 +1,130 @@ +// +// 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.network.nicira; + +import java.util.UUID; + +/** + * + */ +public abstract class NatRule { + protected Match match; + protected UUID uuid; + protected String type; + protected int order; + + public NatRule() { + } + + public Match getMatch() { + return match; + } + + public void setMatch(final Match match) { + this.match = match; + } + + public UUID getUuid() { + return uuid; + } + + public void setUuid(final UUID uuid) { + this.uuid = uuid; + } + + public String getType() { + return type; + } + + public void setType(final String type) { + this.type = type; + } + + public int getOrder() { + return order; + } + + public void setOrder(final int order) { + this.order = order; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((match == null) ? 0 : match.hashCode()); + result = prime * result + order; + result = prime * result + ((type == null) ? 0 : type.hashCode()); + result = prime * result + ((uuid == null) ? 0 : uuid.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + NatRule other = (NatRule)obj; + if (match == null) { + if (other.match != null) + return false; + } else if (!match.equals(other.match)) + return false; + if (order != other.order) + return false; + if (type == null) { + if (other.type != null) + return false; + } else if (!type.equals(other.type)) + return false; + if (uuid == null) { + if (other.uuid != null) + return false; + } else if (!uuid.equals(other.uuid)) + return false; + return true; + } + + public boolean equalsIgnoreUuid(final Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + NatRule other = (NatRule)obj; + if (match == null) { + if (other.match != null) + return false; + } else if (!match.equals(other.match)) + return false; + if (order != other.order) + return false; + if (type == null) { + if (other.type != null) + return false; + } else if (!type.equals(other.type)) + return false; + return true; + } +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NatRuleAdapter.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NatRuleAdapter.java new file mode 100644 index 0000000000..7ea2f5b23b --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NatRuleAdapter.java @@ -0,0 +1,49 @@ +// +// 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.network.nicira; + +import java.lang.reflect.Type; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; + +public class NatRuleAdapter implements JsonDeserializer { + + @Override + public NatRule deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context) throws JsonParseException { + final JsonObject jsonObject = jsonElement.getAsJsonObject(); + + if (!jsonObject.has("type")) { + throw new JsonParseException("Deserializing as a NatRule, but no type present in the json object"); + } + + final String natRuleType = jsonObject.get("type").getAsString(); + if ("SourceNatRule".equals(natRuleType)) { + return context.deserialize(jsonElement, SourceNatRule.class); + } else if ("DestinationNatRule".equals(natRuleType)) { + return context.deserialize(jsonElement, DestinationNatRule.class); + } + + throw new JsonParseException("Failed to deserialize type \"" + natRuleType + "\""); + } +} \ No newline at end of file diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraConstants.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraConstants.java new file mode 100644 index 0000000000..31adf9d3e2 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraConstants.java @@ -0,0 +1,42 @@ +// +// 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.network.nicira; + +public class NiciraConstants { + + public static final String SEC_PROFILE_URI_PREFIX = "/ws.v1/security-profile"; + public static final String ACL_URI_PREFIX = "/ws.v1/acl"; + public static final String SWITCH_URI_PREFIX = "/ws.v1/lswitch"; + public static final String ROUTER_URI_PREFIX = "/ws.v1/lrouter"; + public static final String LOGIN_URL = "/ws.v1/login"; + public static final String CONTROL_CLUSTER_STATUS_URL = "/ws.v1/control-cluster/status"; + + public static final String ATTACHMENT_PATH_SEGMENT = "/attachment"; + public static final String NAT_PATH_SEGMENT = "/nat"; + public static final String LPORT_PATH_SEGMENT = "/lport"; + + public static final String ATTACHMENT_VIF_UUID_QUERY_PARAMETER_NAME = "attachment_vif_uuid"; + public static final String ATTACHMENT_VLAN_PARAMETER = "attachment_vlan"; + public static final String ATTACHMENT_GWSVC_UUID_QUERY_PARAMETER = "attachment_gwsvc_uuid"; + public static final String WILDCARD_QUERY_PARAMETER = "*"; + public static final String UUID_QUERY_PARAMETER = "uuid"; + public static final String FIELDS_QUERY_PARAMETER = "fields"; + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraNvpApi.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraNvpApi.java new file mode 100644 index 0000000000..2d448375d6 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraNvpApi.java @@ -0,0 +1,627 @@ +// +// 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.network.nicira; + +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import com.cloud.utils.rest.CloudstackRESTException; +import com.cloud.utils.rest.RESTServiceConnector; +import com.google.common.base.Optional; +import com.google.gson.JsonDeserializer; +import com.google.gson.reflect.TypeToken; + +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.impl.client.CloseableHttpClient; + +@SuppressWarnings("rawtypes") +public class NiciraNvpApi { + + private static final Optional ABSENT = Optional.absent(); + + private static final String SWITCH_URI_PREFIX = NiciraConstants.SWITCH_URI_PREFIX; + private static final String ROUTER_URI_PREFIX = NiciraConstants.ROUTER_URI_PREFIX; + + private static final String ATTACHMENT_PATH_SEGMENT = NiciraConstants.ATTACHMENT_PATH_SEGMENT; + private static final String NAT_PATH_SEGMENT = NiciraConstants.NAT_PATH_SEGMENT; + private static final String LPORT_PATH_SEGMENT = NiciraConstants.LPORT_PATH_SEGMENT; + + private static final String ATTACHMENT_GWSVC_UUID_QUERY_PARAMETER = NiciraConstants.ATTACHMENT_GWSVC_UUID_QUERY_PARAMETER; + private static final String WILDCARD_QUERY_PARAMETER = NiciraConstants.WILDCARD_QUERY_PARAMETER; + private static final String UUID_QUERY_PARAMETER = NiciraConstants.UUID_QUERY_PARAMETER; + private static final String FIELDS_QUERY_PARAMETER = NiciraConstants.FIELDS_QUERY_PARAMETER; + + private static final int DEFAULT_MAX_RETRIES = 5; + + private final RESTServiceConnector restConnector; + + protected final static Map prefixMap; + + protected final static Map listTypeMap; + + protected final static Map defaultListParams; + + static { + prefixMap = new HashMap(); + prefixMap.put(SecurityProfile.class, NiciraConstants.SEC_PROFILE_URI_PREFIX); + prefixMap.put(Acl.class, NiciraConstants.ACL_URI_PREFIX); + prefixMap.put(LogicalSwitch.class, SWITCH_URI_PREFIX); + prefixMap.put(LogicalRouter.class, ROUTER_URI_PREFIX); + + listTypeMap = new HashMap(); + listTypeMap.put(SecurityProfile.class, new TypeToken>() { + }.getType()); + listTypeMap.put(Acl.class, new TypeToken>() { + }.getType()); + listTypeMap.put(LogicalSwitch.class, new TypeToken>() { + }.getType()); + listTypeMap.put(LogicalRouter.class, new TypeToken>() { + }.getType()); + + defaultListParams = new HashMap(); + defaultListParams.put(FIELDS_QUERY_PARAMETER, WILDCARD_QUERY_PARAMETER); + } + + private NiciraNvpApi(final Builder builder) { + final Map, JsonDeserializer> classToDeserializerMap = new HashMap<>(); + classToDeserializerMap.put(NatRule.class, new NatRuleAdapter()); + classToDeserializerMap.put(RoutingConfig.class, new RoutingConfigAdapter()); + + final NiciraRestClient niciraRestClient = NiciraRestClient.create() + .client(builder.httpClient) + .clientContext(builder.httpClientContext) + .hostname(builder.host) + .username(builder.username) + .password(builder.password) + .loginUrl(NiciraConstants.LOGIN_URL) + .executionLimit(DEFAULT_MAX_RETRIES) + .build(); + restConnector = RESTServiceConnector.create() + .classToDeserializerMap(classToDeserializerMap) + .client(niciraRestClient) + .build(); + } + + public static Builder create() { + return new Builder(); + } + + /** + * POST + * + * @param entity + * @return + * @throws NiciraNvpApiException + */ + private T create(final T entity) throws NiciraNvpApiException { + final String uri = prefixMap.get(entity.getClass()); + return createWithUri(entity, uri); + } + + /** + * POST + * + * @param entity + * @return + * @throws NiciraNvpApiException + */ + private T createWithUri(final T entity, final String uri) throws NiciraNvpApiException { + T createdEntity; + try { + createdEntity = restConnector.executeCreateObject(entity, uri, Collections. emptyMap()); + } catch (final CloudstackRESTException e) { + throw new NiciraNvpApiException(e); + } + + return createdEntity; + } + + /** + * GET list of items + * + * @param uuid + * + * @return + * @throws NiciraNvpApiException + */ + private List find(final Optional uuid, final Class clazz) throws NiciraNvpApiException { + final String uri = prefixMap.get(clazz); + Map params = defaultListParams; + if (uuid.isPresent()) { + params = new HashMap(defaultListParams); + params.put(UUID_QUERY_PARAMETER, uuid.get()); + } + + NiciraNvpList entities; + try { + entities = restConnector.executeRetrieveObject(listTypeMap.get(clazz), uri, params); + } catch (final CloudstackRESTException e) { + throw new NiciraNvpApiException(e); + } + + if (entities == null) { + throw new NiciraNvpApiException("Unexpected response from API"); + } + + return entities.getResults(); + } + + /** + * PUT item given a UUID as key and an item object with the new data + * + * @param item + * @param uuid + * @throws NiciraNvpApiException + */ + private void update(final T item, final String uuid) throws NiciraNvpApiException { + final String uri = prefixMap.get(item.getClass()) + "/" + uuid; + updateWithUri(item, uri); + } + + /** + * PUT item given a UUID as key and an item object with the new data + * + * @param item + * @param uuid + * @throws NiciraNvpApiException + */ + private void updateWithUri(final T item, final String uri) throws NiciraNvpApiException { + try { + restConnector.executeUpdateObject(item, uri, Collections. emptyMap()); + } catch (final CloudstackRESTException e) { + throw new NiciraNvpApiException(e); + } + } + + /** + * DELETE Security Profile given a UUID as key + * + * @param securityProfileUuid + * @throws NiciraNvpApiException + */ + private void delete(final String uuid, final Class clazz) throws NiciraNvpApiException { + final String uri = prefixMap.get(clazz) + "/" + uuid; + deleteWithUri(uri); + } + + /** + * DELETE Security Profile given a UUID as key + * + * @param securityProfileUuid + * @throws NiciraNvpApiException + */ + private void deleteWithUri(final String uri) throws NiciraNvpApiException { + try { + restConnector.executeDeleteObject(uri); + } catch (final CloudstackRESTException e) { + throw new NiciraNvpApiException(e); + } + } + + /** + * POST {@link SecurityProfile} + * + * @param securityProfile + * @return + * @throws NiciraNvpApiException + */ + public SecurityProfile createSecurityProfile(final SecurityProfile securityProfile) throws NiciraNvpApiException { + return create(securityProfile); + } + + /** + * GET list of {@link SecurityProfile} + * + * @return + * @throws NiciraNvpApiException + */ + public List findSecurityProfile() throws NiciraNvpApiException { + return find(ABSENT, SecurityProfile.class); + } + + /** + * GET list of {@link SecurityProfile} filtered by UUID + * + * We could have invoked the service: SEC_PROFILE_URI_PREFIX + "/" + securityProfileUuid but it is not working currently + * + * @param uuid + * @return + * @throws NiciraNvpApiException + */ + public List findSecurityProfile(final String uuid) throws NiciraNvpApiException { + return find(Optional.fromNullable(uuid), SecurityProfile.class); + } + + /** + * PUT {@link SecurityProfile} given a UUID as key and a {@link SecurityProfile} with the new data + * + * @param securityProfile + * @param securityProfileUuid + * @throws NiciraNvpApiException + */ + public void updateSecurityProfile(final SecurityProfile securityProfile, final String securityProfileUuid) throws NiciraNvpApiException { + update(securityProfile, securityProfileUuid); + } + + /** + * DELETE Security Profile given a UUID as key + * + * @param securityProfileUuid + * @throws NiciraNvpApiException + */ + public void deleteSecurityProfile(final String securityProfileUuid) throws NiciraNvpApiException { + delete(securityProfileUuid, SecurityProfile.class); + } + + /** + * POST {@link Acl} + * + * @param acl + * @return + * @throws NiciraNvpApiException + */ + public Acl createAcl(final Acl acl) throws NiciraNvpApiException { + return create(acl); + } + + /** + * GET list of {@link Acl} + * + * @return + * @throws NiciraNvpApiException + */ + public List findAcl() throws NiciraNvpApiException { + return findAcl(null); + } + + /** + * GET list of {@link Acl} filtered by UUID + * + * @param uuid + * @return + * @throws NiciraNvpApiException + */ + public List findAcl(final String uuid) throws NiciraNvpApiException { + return find(Optional.fromNullable(uuid), Acl.class); + } + + /** + * PUT {@link Acl} given a UUID as key and a {@link Acl} with the new data + * + * @param acl + * @param aclUuid + * @throws NiciraNvpApiException + */ + public void updateAcl(final Acl acl, final String aclUuid) throws NiciraNvpApiException { + update(acl, aclUuid); + } + + /** + * DELETE Acl given a UUID as key + * + * @param acl + * @throws NiciraNvpApiException + */ + public void deleteAcl(final String aclUuid) throws NiciraNvpApiException { + delete(aclUuid, Acl.class); + } + + public LogicalSwitch createLogicalSwitch(final LogicalSwitch logicalSwitch) throws NiciraNvpApiException { + return create(logicalSwitch); + } + + /** + * GET list of {@link LogicalSwitch} + * + * @return + * @throws NiciraNvpApiException + */ + public List findLogicalSwitch() throws NiciraNvpApiException { + return findLogicalSwitch(null); + } + + /** + * GET list of {@link LogicalSwitch} filtered by UUID + * + * @param uuid + * @return + * @throws NiciraNvpApiException + */ + public List findLogicalSwitch(final String uuid) throws NiciraNvpApiException { + return find(Optional.fromNullable(uuid), LogicalSwitch.class); + } + + /** + * PUT {@link LogicalSwitch} given a UUID as key and a {@link LogicalSwitch} with the new data + * + * @param logicalSwitch + * @param logicalSwitchUuid + * @throws NiciraNvpApiException + */ + public void updateLogicalSwitch(final LogicalSwitch logicalSwitch, final String logicalSwitchUuid) throws NiciraNvpApiException { + update(logicalSwitch, logicalSwitchUuid); + } + + public void deleteLogicalSwitch(final String uuid) throws NiciraNvpApiException { + delete(uuid, LogicalSwitch.class); + } + + public LogicalSwitchPort createLogicalSwitchPort(final String logicalSwitchUuid, final LogicalSwitchPort logicalSwitchPort) throws NiciraNvpApiException { + return createWithUri(logicalSwitchPort, buildLogicalSwitchElementUri(logicalSwitchUuid, LPORT_PATH_SEGMENT)); + } + + public void updateLogicalSwitchPort(final String logicalSwitchUuid, final LogicalSwitchPort logicalSwitchPort) throws NiciraNvpApiException { + updateWithUri(logicalSwitchPort, buildLogicalSwitchElementUri(logicalSwitchUuid, LPORT_PATH_SEGMENT, logicalSwitchPort.getUuid().toString())); + } + + public void updateLogicalSwitchPortAttachment(final String logicalSwitchUuid, final String logicalSwitchPortUuid, final Attachment attachment) throws NiciraNvpApiException { + updateWithUri(attachment, buildLogicalSwitchElementUri(logicalSwitchUuid, LPORT_PATH_SEGMENT, logicalSwitchPortUuid) + ATTACHMENT_PATH_SEGMENT); + } + + public void deleteLogicalSwitchPort(final String logicalSwitchUuid, final String logicalSwitchPortUuid) throws NiciraNvpApiException { + deleteWithUri(buildLogicalSwitchElementUri(logicalSwitchUuid, LPORT_PATH_SEGMENT, logicalSwitchPortUuid)); + } + + public String findLogicalSwitchPortUuidByVifAttachmentUuid(final String logicalSwitchUuid, final String vifAttachmentUuid) throws NiciraNvpApiException { + final String uri = buildLogicalSwitchElementUri(logicalSwitchUuid, LPORT_PATH_SEGMENT); + final Map params = buildBasicParametersMap(UUID_QUERY_PARAMETER); + params.put(NiciraConstants.ATTACHMENT_VIF_UUID_QUERY_PARAMETER_NAME, vifAttachmentUuid); + + NiciraNvpList niciraList; + try { + final Type niciraListType = new TypeToken>() { + }.getType(); + niciraList = restConnector.executeRetrieveObject(niciraListType, uri, params); + } catch (final CloudstackRESTException e) { + throw new NiciraNvpApiException(e); + } + + final List lspl = niciraList.getResults(); + + final int listSize = lspl.size(); + if (listSize != 1) { + throw new NiciraNvpApiException("Expected 1 LogicalSwitchPort, but got " + listSize); + } + + final LogicalSwitchPort lsp = lspl.get(0); + return lsp.getUuid(); + } + + public ControlClusterStatus getControlClusterStatus() throws NiciraNvpApiException { + final String uri = NiciraConstants.CONTROL_CLUSTER_STATUS_URL; + try { + return restConnector.executeRetrieveObject(ControlClusterStatus.class, uri, new HashMap()); + } catch (final CloudstackRESTException e) { + throw new NiciraNvpApiException(e); + } + } + + public List findLogicalSwitchPortsByUuid(final String logicalSwitchUuid, final String logicalSwitchPortUuid) throws NiciraNvpApiException { + final String uri = buildLogicalSwitchElementUri(logicalSwitchUuid, LPORT_PATH_SEGMENT); + final Map params = buildBasicParametersMap(UUID_QUERY_PARAMETER); + params.put(UUID_QUERY_PARAMETER, logicalSwitchPortUuid); + + try { + final Type niciraListType = new TypeToken>() { + }.getType(); + return restConnector.> executeRetrieveObject(niciraListType, uri, params).getResults(); + } catch (final CloudstackRESTException e) { + throw new NiciraNvpApiException(e); + } + } + + public List findLogicalRouterPortsByUuid(final String logicalRouterUuid, final String logicalRouterPortUuid) throws NiciraNvpApiException { + final String uri = buildLogicalRouterElementUri(logicalRouterUuid, LPORT_PATH_SEGMENT); + final Map params = buildBasicParametersMap(UUID_QUERY_PARAMETER); + params.put(UUID_QUERY_PARAMETER, logicalRouterPortUuid); + + try { + final Type niciraListType = new TypeToken>() { + }.getType(); + return restConnector.> executeRetrieveObject(niciraListType, uri, params).getResults(); + } catch (final CloudstackRESTException e) { + throw new NiciraNvpApiException(e); + } + } + + public LogicalRouter createLogicalRouter(final LogicalRouter logicalRouter) throws NiciraNvpApiException { + return create(logicalRouter); + } + + /** + * GET list of {@link LogicalRouter} + * + * @return + * @throws NiciraNvpApiException + */ + public List findLogicalRouter() throws NiciraNvpApiException { + return findLogicalRouter(null); + } + + /** + * GET list of {@link LogicalRouter} filtered by UUID + * + * @param uuid + * @return + * @throws NiciraNvpApiException + */ + public List findLogicalRouter(final String uuid) throws NiciraNvpApiException { + return find(Optional.fromNullable(uuid), LogicalRouter.class); + } + + public LogicalRouter findOneLogicalRouterByUuid(final String logicalRouterUuid) throws NiciraNvpApiException { + return findLogicalRouter(logicalRouterUuid).get(0); + } + + public void updateLogicalRouter(final LogicalRouter logicalRouter, final String logicalRouterUuid) throws NiciraNvpApiException { + update(logicalRouter, logicalRouterUuid); + } + + public void deleteLogicalRouter(final String logicalRouterUuid) throws NiciraNvpApiException { + deleteWithUri(buildLogicalRouterUri(logicalRouterUuid)); + } + + public LogicalRouterPort createLogicalRouterPort(final String logicalRouterUuid, final LogicalRouterPort logicalRouterPort) throws NiciraNvpApiException { + return createWithUri(logicalRouterPort, buildLogicalRouterElementUri(logicalRouterUuid, LPORT_PATH_SEGMENT)); + } + + public void deleteLogicalRouterPort(final String logicalRouterUuid, final String logicalRouterPortUuid) throws NiciraNvpApiException { + deleteWithUri(buildLogicalRouterElementUri(logicalRouterUuid, LPORT_PATH_SEGMENT, logicalRouterPortUuid)); + } + + public void updateLogicalRouterPort(final String logicalRouterUuid, final LogicalRouterPort logicalRouterPort) throws NiciraNvpApiException { + updateWithUri(logicalRouterPort, buildLogicalRouterElementUri(logicalRouterUuid, LPORT_PATH_SEGMENT, logicalRouterPort.getUuid().toString())); + } + + public void updateLogicalRouterPortAttachment(final String logicalRouterUuid, final String logicalRouterPortUuid, final Attachment attachment) throws NiciraNvpApiException { + updateWithUri(attachment, buildLogicalRouterElementUri(logicalRouterUuid, LPORT_PATH_SEGMENT, logicalRouterPortUuid) + ATTACHMENT_PATH_SEGMENT); + } + + public NatRule createLogicalRouterNatRule(final String logicalRouterUuid, final NatRule natRule) throws NiciraNvpApiException { + return createWithUri(natRule, buildLogicalRouterElementUri(logicalRouterUuid, NAT_PATH_SEGMENT)); + } + + public void updateLogicalRouterNatRule(final String logicalRouterUuid, final NatRule natRule) throws NiciraNvpApiException { + updateWithUri(natRule, buildLogicalRouterElementUri(logicalRouterUuid, NAT_PATH_SEGMENT, natRule.getUuid().toString())); + } + + public void deleteLogicalRouterNatRule(final String logicalRouterUuid, final UUID natRuleUuid) throws NiciraNvpApiException { + deleteWithUri(buildLogicalRouterElementUri(logicalRouterUuid, NAT_PATH_SEGMENT, natRuleUuid.toString())); + } + + public List findLogicalRouterPortByGatewayServiceAndVlanId(final String logicalRouterUuid, final String gatewayServiceUuid, final long vlanId) + throws NiciraNvpApiException { + final String uri = buildLogicalRouterElementUri(logicalRouterUuid, LPORT_PATH_SEGMENT); + final Map params = buildBasicParametersMap(WILDCARD_QUERY_PARAMETER); + params.put(ATTACHMENT_GWSVC_UUID_QUERY_PARAMETER, gatewayServiceUuid); + params.put(NiciraConstants.ATTACHMENT_VLAN_PARAMETER, Long.toString(vlanId)); + + try { + final Type niciraListType = new TypeToken>() { + }.getType(); + return restConnector.> executeRetrieveObject(niciraListType, uri, params).getResults(); + } catch (final CloudstackRESTException e) { + throw new NiciraNvpApiException(e); + } + } + + public List findNatRulesByLogicalRouterUuid(final String logicalRouterUuid) throws NiciraNvpApiException { + final String uri = buildLogicalRouterElementUri(logicalRouterUuid, NAT_PATH_SEGMENT); + final Map params = buildBasicParametersMap(WILDCARD_QUERY_PARAMETER); + + try { + final Type niciraListType = new TypeToken>() { + }.getType(); + return restConnector.> executeRetrieveObject(niciraListType, uri, params).getResults(); + } catch (final CloudstackRESTException e) { + throw new NiciraNvpApiException(e); + } + } + + public List findLogicalRouterPortByGatewayServiceUuid(final String logicalRouterUuid, final String l3GatewayServiceUuid) + throws NiciraNvpApiException { + final String uri = buildLogicalRouterElementUri(logicalRouterUuid, LPORT_PATH_SEGMENT); + final Map params = buildBasicParametersMap(WILDCARD_QUERY_PARAMETER); + params.put(ATTACHMENT_GWSVC_UUID_QUERY_PARAMETER, l3GatewayServiceUuid); + + try { + final Type niciraListType = new TypeToken>() { + }.getType(); + return restConnector.> executeRetrieveObject(niciraListType, uri, params).getResults(); + } catch (final CloudstackRESTException e) { + throw new NiciraNvpApiException(e); + } + } + + private static Map buildBasicParametersMap(final String fieldsQueryValue) { + final Map params = new HashMap(); + params.put(FIELDS_QUERY_PARAMETER, fieldsQueryValue); + return params; + } + + private static String buildUri(final String uriPrefix, final String uuid) { + return uriPrefix + "/" + uuid; + } + + private static String buildLogicalSwitchUri(final String logicalSwitchUuid) { + return buildUri(SWITCH_URI_PREFIX, logicalSwitchUuid); + } + + private static String buildLogicalSwitchElementUri(final String logicalSwitchUuid, final String logicalElementType) { + return buildLogicalSwitchUri(logicalSwitchUuid) + logicalElementType; + } + + private static String buildLogicalSwitchElementUri(final String logicalSwitchUuid, final String logicalElementType, final String elementUuid) { + return buildLogicalSwitchElementUri(logicalSwitchUuid, logicalElementType) + "/" + elementUuid.toString(); + } + + private static String buildLogicalRouterUri(final String logicalRouterUuid) { + return buildUri(ROUTER_URI_PREFIX, logicalRouterUuid); + } + + private static String buildLogicalRouterElementUri(final String logicalRouterUuid, final String logicalElementType) { + return buildLogicalRouterUri(logicalRouterUuid) + logicalElementType; + } + + private static String buildLogicalRouterElementUri(final String logicalRouterUuid, final String logicalRouterElementType, final String elementUuid) { + return buildLogicalRouterElementUri(logicalRouterUuid, logicalRouterElementType) + "/" + elementUuid.toString(); + } + + public static class Builder { + private String host; + private String username; + private String password; + private CloseableHttpClient httpClient; + private HttpClientContext httpClientContext = HttpClientContext.create(); + + public Builder host(final String host) { + this.host = host; + return this; + } + + public Builder username(final String username) { + this.username = username; + return this; + } + + public Builder password(final String password) { + this.password = password; + return this; + } + + public Builder httpClient(final CloseableHttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + + public Builder httpClientContext(final HttpClientContext httpClientContext) { + this.httpClientContext = httpClientContext; + return this; + } + + public NiciraNvpApi build() { + return new NiciraNvpApi(this); + } + } +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraNvpApiException.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraNvpApiException.java new file mode 100644 index 0000000000..0d2782007f --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraNvpApiException.java @@ -0,0 +1,39 @@ +// +// 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.network.nicira; + +public class NiciraNvpApiException extends Exception { + + public NiciraNvpApiException() { + } + + public NiciraNvpApiException(final String message) { + super(message); + } + + public NiciraNvpApiException(final Throwable cause) { + super(cause); + } + + public NiciraNvpApiException(final String message, final Throwable cause) { + super(message, cause); + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraNvpList.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraNvpList.java new file mode 100644 index 0000000000..34b5e19c8c --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraNvpList.java @@ -0,0 +1,48 @@ +// +// 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.network.nicira; + +import java.util.List; + +public class NiciraNvpList { + private List results; + private int resultCount; + + public List getResults() { + return this.results; + } + + public void setResults(List results) { + this.results = results; + } + + public int getResultCount() { + return resultCount; + } + + public void setResultCount(int resultCount) { + this.resultCount = resultCount; + } + + public boolean isEmpty() { + return this.resultCount == 0; + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraNvpTag.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraNvpTag.java new file mode 100644 index 0000000000..1ba5dbe2bf --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraNvpTag.java @@ -0,0 +1,65 @@ +// +// 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.network.nicira; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class NiciraNvpTag { + private static final int TAG_MAX_LEN = 40; + private static final Logger s_logger = LoggerFactory.getLogger(NiciraNvpTag.class); + private String scope; + private String tag; + + public NiciraNvpTag() { + } + + public NiciraNvpTag(String scope, String tag) { + this.scope = scope; + if (tag.length() > 40) { + s_logger.warn("tag \"" + tag + "\" too long, truncating to 40 characters"); + this.tag = tag.substring(0, TAG_MAX_LEN); + } else { + this.tag = tag; + } + } + + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope; + } + + public String getTag() { + return tag; + } + + public void setTag(String tag) { + if (tag.length() > 40) { + s_logger.warn("tag \"" + tag + "\" too long, truncating to 40 characters"); + this.tag = tag.substring(0, 40); + } else { + this.tag = tag; + } + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraRestClient.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraRestClient.java new file mode 100644 index 0000000000..fdcf5c1e1c --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/NiciraRestClient.java @@ -0,0 +1,205 @@ +// +// 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.network.nicira; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import com.cloud.utils.rest.BasicRestClient; +import com.cloud.utils.rest.CloudstackRESTException; +import com.cloud.utils.rest.HttpConstants; +import com.cloud.utils.rest.HttpMethods; +import com.cloud.utils.rest.HttpStatusCodeHelper; +import com.cloud.utils.rest.HttpUriRequestBuilder; + +import org.apache.http.HttpEntity; +import org.apache.http.StatusLine; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class NiciraRestClient extends BasicRestClient { + + private static final Logger s_logger = LoggerFactory.getLogger(NiciraRestClient.class); + + private static final String CONTENT_TYPE = HttpConstants.CONTENT_TYPE; + private static final String TEXT_HTML_CONTENT_TYPE = HttpConstants.TEXT_HTML_CONTENT_TYPE; + + private static final int DEFAULT_BODY_RESP_MAX_LEN = 1024; + private static final int DEFAULT_EXECUTION_LIMIT = 5; + + private final ExecutionCounter counter; + private final int maxResponseErrorMesageLength; + private final int executionLimit; + + private final String username; + private final String password; + private final String loginUrl; + + private NiciraRestClient(final Builder builder) { + super(builder.client, builder.clientContext, builder.hostname); + executionLimit = builder.executionLimit; + counter = new ExecutionCounter(executionLimit); + maxResponseErrorMesageLength = builder.maxResponseErrorMesageLength; + username = builder.username; + password = builder.password; + loginUrl = builder.loginUrl; + } + + public static Builder create() { + return new Builder(); + } + + @Override + public CloseableHttpResponse execute(final HttpUriRequest request) throws CloudstackRESTException { + return execute(request, 0); + } + + private CloseableHttpResponse execute(final HttpUriRequest request, final int previousStatusCode) throws CloudstackRESTException { + if (counter.hasReachedExecutionLimit()) { + throw new CloudstackRESTException("Reached max executions limit of " + executionLimit); + } + counter.incrementExecutionCounter(); + s_logger.debug("Executing " + request.getMethod() + " request [execution count = " + counter.getValue() + "]"); + final CloseableHttpResponse response = super.execute(request); + + final StatusLine statusLine = response.getStatusLine(); + final int statusCode = statusLine.getStatusCode(); + s_logger.debug("Status of last request: " + statusLine.toString()); + if (HttpStatusCodeHelper.isUnauthorized(statusCode)) { + return handleUnauthorizedResponse(request, previousStatusCode, response, statusCode); + } else if (HttpStatusCodeHelper.isSuccess(statusCode)) { + return handleSuccessResponse(request, response); + } else { + throw new CloudstackRESTException("Unexpecetd status code: " + statusCode); + } + } + + private CloseableHttpResponse handleUnauthorizedResponse(final HttpUriRequest request, final int previousStatusCode, final CloseableHttpResponse response, final int statusCode) + throws CloudstackRESTException { + super.closeResponse(response); + if (HttpStatusCodeHelper.isUnauthorized(previousStatusCode)) { + s_logger.error(responseToErrorMessage(response)); + throw new CloudstackRESTException("Two consecutive failed attempts to authenticate against REST server"); + } + final HttpUriRequest authenticateRequest = createAuthenticationRequest(); + final CloseableHttpResponse loginResponse = execute(authenticateRequest, statusCode); + final int loginStatusCode = loginResponse.getStatusLine().getStatusCode(); + super.closeResponse(loginResponse); + return execute(request, loginStatusCode); + } + + private CloseableHttpResponse handleSuccessResponse(final HttpUriRequest request, final CloseableHttpResponse response) { + if (!request.getURI().getPath().contains(loginUrl)) { + counter.resetExecutionCounter(); + } + return response; + } + + private HttpUriRequest createAuthenticationRequest() { + final Map parameters = new HashMap<>(); + parameters.put("username", username); + parameters.put("password", password); + return HttpUriRequestBuilder.create() + .method(HttpMethods.POST) + .methodParameters(parameters) + .path(loginUrl) + .build(); + } + + private String responseToErrorMessage(final CloseableHttpResponse response) { + String errorMessage = response.getStatusLine().toString(); + if (response.containsHeader(CONTENT_TYPE) && TEXT_HTML_CONTENT_TYPE.equals(response.getFirstHeader(CONTENT_TYPE).getValue())) { + try { + final HttpEntity entity = response.getEntity(); + final String respobnseBody = EntityUtils.toString(entity); + errorMessage = respobnseBody.subSequence(0, maxResponseErrorMesageLength).toString(); + } catch (final IOException e) { + s_logger.debug("Could not read repsonse body. Response: " + response, e); + } + } + + return errorMessage; + } + + protected static class Builder extends BasicRestClient.Builder { + private CloseableHttpClient client; + private HttpClientContext clientContext; + private String hostname; + private String username; + private String password; + private String loginUrl; + private int executionLimit = DEFAULT_EXECUTION_LIMIT; + private int maxResponseErrorMesageLength = DEFAULT_BODY_RESP_MAX_LEN; + + public Builder hostname(final String hostname) { + this.hostname = hostname; + return this; + } + + public Builder username(final String username) { + this.username = username; + return this; + } + + public Builder password(final String password) { + this.password = password; + return this; + } + + public Builder loginUrl(final String loginUrl) { + this.loginUrl = loginUrl; + return this; + } + + @Override + public Builder client(final CloseableHttpClient client) { + this.client = client; + return this; + } + + @Override + public Builder clientContext(final HttpClientContext clientContext) { + this.clientContext = clientContext; + return this; + } + + public Builder executionLimit(final int executionLimit) { + this.executionLimit = executionLimit; + return this; + } + + public Builder maxResponseErrorMesageLength(final int maxResponseErrorMesageLength) { + this.maxResponseErrorMesageLength = maxResponseErrorMesageLength; + return this; + } + + @Override + public NiciraRestClient build() { + return new NiciraRestClient(this); + } + + } +} \ No newline at end of file diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/PatchAttachment.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/PatchAttachment.java new file mode 100644 index 0000000000..26fa048611 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/PatchAttachment.java @@ -0,0 +1,41 @@ +// +// 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.network.nicira; + +/** + * + */ +public class PatchAttachment extends Attachment { + private final String type = "PatchAttachment"; + private String peerPortUuid; + + public PatchAttachment(String peerPortUuid) { + this.peerPortUuid = peerPortUuid; + } + + public String getPeerPortUuid() { + return peerPortUuid; + } + + public void setPeerPortUuid(String peerPortUuid) { + this.peerPortUuid = peerPortUuid; + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/RouterNextHop.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/RouterNextHop.java new file mode 100644 index 0000000000..8dd884072b --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/RouterNextHop.java @@ -0,0 +1,40 @@ +// +// 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.network.nicira; + +/** + * + */ +public class RouterNextHop { + private String gatewayIpAddress; + private final String type = "RouterNextHop"; + + public RouterNextHop(String gatewayIpAddress) { + this.gatewayIpAddress = gatewayIpAddress; + } + + public String getGatewayIpAddress() { + return gatewayIpAddress; + } + + public void setGatewayIpAddress(String gatewayIpAddress) { + this.gatewayIpAddress = gatewayIpAddress; + } +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/RoutingConfig.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/RoutingConfig.java new file mode 100644 index 0000000000..5eaef0a699 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/RoutingConfig.java @@ -0,0 +1,24 @@ +// +// 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.network.nicira; + +public class RoutingConfig { + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/RoutingConfigAdapter.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/RoutingConfigAdapter.java new file mode 100644 index 0000000000..ad94f6343d --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/RoutingConfigAdapter.java @@ -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. +// + +package com.cloud.network.nicira; + +import java.lang.reflect.Type; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; + +public class RoutingConfigAdapter implements JsonDeserializer { + + private static final String ROUTING_TABLE_ROUTING_CONFIG = "RoutingTableRoutingConfig"; + private static final String SINGLE_DEFAULT_ROUTE_IMPLICIT_ROUTING_CONFIG = "SingleDefaultRouteImplicitRoutingConfig"; + + @Override + public RoutingConfig deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context) throws JsonParseException { + final JsonObject jsonObject = jsonElement.getAsJsonObject(); + + if (!jsonObject.has("type")) { + throw new JsonParseException("Deserializing as a RoutingConfig, but no type present in the json object"); + } + + final String routingConfigType = jsonObject.get("type").getAsString(); + if (SINGLE_DEFAULT_ROUTE_IMPLICIT_ROUTING_CONFIG.equals(routingConfigType)) { + return context.deserialize(jsonElement, SingleDefaultRouteImplicitRoutingConfig.class); + } else if (ROUTING_TABLE_ROUTING_CONFIG.equals(routingConfigType)) { + return context.deserialize(jsonElement, RoutingTableRoutingConfig.class); + } + + throw new JsonParseException("Failed to deserialize type \"" + routingConfigType + "\""); + } +} \ No newline at end of file diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/RoutingTableRoutingConfig.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/RoutingTableRoutingConfig.java new file mode 100644 index 0000000000..f0a80b85e5 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/RoutingTableRoutingConfig.java @@ -0,0 +1,30 @@ +// +// 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.network.nicira; + +/** + * + */ +public class RoutingTableRoutingConfig extends RoutingConfig { + public final String type = "RoutingTableRoutingConfig"; + + public RoutingTableRoutingConfig() { + } +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/SecurityProfile.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/SecurityProfile.java new file mode 100644 index 0000000000..d83ce49db1 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/SecurityProfile.java @@ -0,0 +1,24 @@ +// +// 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.network.nicira; + +@SuppressWarnings("serial") +public class SecurityProfile extends AccessConfiguration { +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/SecurityRule.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/SecurityRule.java new file mode 100644 index 0000000000..e23593cb3d --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/SecurityRule.java @@ -0,0 +1,138 @@ +// +// 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.network.nicira; + +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; + +@SuppressWarnings("serial") +public class SecurityRule extends AccessRule { + + protected String ipPrefix; + + protected int portRangeMin; + + protected int portRangeMax; + + protected String profileUuid; + + + /** + * Default constructor + */ + public SecurityRule() { + } + + /** + * Fully parameterized constructor + */ + public SecurityRule(final String ethertype, final String ipPrefix, final String profileUuid, + final int portRangeMin, final int portRangeMax, final int protocol) { + this.ethertype = ethertype; + this.ipPrefix = ipPrefix; + this.portRangeMin = portRangeMin; + this.portRangeMax = portRangeMax; + this.profileUuid = profileUuid; + this.protocol = protocol; + } + + @Override + public String getEthertype() { + return ethertype; + } + + @Override + public void setEthertype(final String ethertype) { + this.ethertype = ethertype; + } + + public String getIpPrefix() { + return ipPrefix; + } + + public void setIpPrefix(final String ipPrefix) { + this.ipPrefix = ipPrefix; + } + + public int getPortRangeMin() { + return portRangeMin; + } + + public void setPortRangeMin(final int portRangeMin) { + this.portRangeMin = portRangeMin; + } + + public int getPortRangeMax() { + return portRangeMax; + } + + public void setPortRangeMax(final int portRangeMax) { + this.portRangeMax = portRangeMax; + } + + public String getProfileUuid() { + return profileUuid; + } + + public void setProfileUuid(final String profileUuid) { + this.profileUuid = profileUuid; + } + + @Override + public int getProtocol() { + return protocol; + } + + @Override + public void setProtocol(final int protocol) { + this.protocol = protocol; + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 31) + .append(ethertype).append(ipPrefix) + .append(portRangeMin).append(portRangeMax) + .append(profileUuid).append(protocol) + .toHashCode(); + } + + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (!(obj instanceof SecurityRule)) { + return false; + } + final SecurityRule another = (SecurityRule) obj; + return new EqualsBuilder() + .append(ethertype, another.ethertype) + .append(ipPrefix, another.ipPrefix) + .append(portRangeMin, another.portRangeMin) + .append(portRangeMax, another.portRangeMax) + .append(profileUuid, another.profileUuid) + .append(protocol, another.protocol) + .isEquals(); + } +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/SingleDefaultRouteImplicitRoutingConfig.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/SingleDefaultRouteImplicitRoutingConfig.java new file mode 100644 index 0000000000..ea5df2cd1f --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/SingleDefaultRouteImplicitRoutingConfig.java @@ -0,0 +1,40 @@ +// +// 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.network.nicira; + +/** + * + */ +public class SingleDefaultRouteImplicitRoutingConfig extends RoutingConfig { + public RouterNextHop defaultRouteNextHop; + public final String type = "SingleDefaultRouteImplicitRoutingConfig"; + + public SingleDefaultRouteImplicitRoutingConfig(final RouterNextHop routerNextHop) { + defaultRouteNextHop = routerNextHop; + } + + public RouterNextHop getDefaultRouteNextHop() { + return defaultRouteNextHop; + } + + public void setDefaultRouteNextHop(final RouterNextHop defaultRouteNextHop) { + this.defaultRouteNextHop = defaultRouteNextHop; + } +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/SourceNatRule.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/SourceNatRule.java new file mode 100644 index 0000000000..b95cb9aeb5 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/SourceNatRule.java @@ -0,0 +1,119 @@ +// +// 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.network.nicira; + +public class SourceNatRule extends NatRule { + private String toSourceIpAddressMax; + private String toSourceIpAddressMin; + private Integer toSourcePort; + + public SourceNatRule() { + setType("SourceNatRule"); + } + + public String getToSourceIpAddressMax() { + return toSourceIpAddressMax; + } + + public void setToSourceIpAddressMax(final String toSourceIpAddressMax) { + this.toSourceIpAddressMax = toSourceIpAddressMax; + } + + public String getToSourceIpAddressMin() { + return toSourceIpAddressMin; + } + + public void setToSourceIpAddressMin(final String toSourceIpAddressMin) { + this.toSourceIpAddressMin = toSourceIpAddressMin; + } + + public Integer getToSourcePort() { + return toSourcePort; + } + + public void setToSourcePort(final Integer toSourcePort) { + this.toSourcePort = toSourcePort; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((toSourceIpAddressMax == null) ? 0 : toSourceIpAddressMax.hashCode()); + result = prime * result + ((toSourceIpAddressMin == null) ? 0 : toSourceIpAddressMin.hashCode()); + result = prime * result + ((toSourcePort == null) ? 0 : toSourcePort.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + SourceNatRule other = (SourceNatRule)obj; + if (toSourceIpAddressMax == null) { + if (other.toSourceIpAddressMax != null) + return false; + } else if (!toSourceIpAddressMax.equals(other.toSourceIpAddressMax)) + return false; + if (toSourceIpAddressMin == null) { + if (other.toSourceIpAddressMin != null) + return false; + } else if (!toSourceIpAddressMin.equals(other.toSourceIpAddressMin)) + return false; + if (toSourcePort == null) { + if (other.toSourcePort != null) + return false; + } else if (!toSourcePort.equals(other.toSourcePort)) + return false; + return true; + } + + @Override + public boolean equalsIgnoreUuid(final Object obj) { + if (this == obj) + return true; + if (!super.equalsIgnoreUuid(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + SourceNatRule other = (SourceNatRule)obj; + if (toSourceIpAddressMax == null) { + if (other.toSourceIpAddressMax != null) + return false; + } else if (!toSourceIpAddressMax.equals(other.toSourceIpAddressMax)) + return false; + if (toSourceIpAddressMin == null) { + if (other.toSourceIpAddressMin != null) + return false; + } else if (!toSourceIpAddressMin.equals(other.toSourceIpAddressMin)) + return false; + if (toSourcePort == null) { + if (other.toSourcePort != null) + return false; + } else if (!toSourcePort.equals(other.toSourcePort)) + return false; + return true; + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/TransportZoneBinding.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/TransportZoneBinding.java new file mode 100644 index 0000000000..c643876d93 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/TransportZoneBinding.java @@ -0,0 +1,50 @@ +// +// 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.network.nicira; + +public class TransportZoneBinding { + private String zoneUuid; + private String transportType; + + public TransportZoneBinding() { + } + + public TransportZoneBinding(String zoneUuid, String transportType) { + this.zoneUuid = zoneUuid; + this.transportType = transportType; + } + + public String getZoneUuid() { + return zoneUuid; + } + + public void setZoneUuid(String zoneUuid) { + this.zoneUuid = zoneUuid; + } + + public String getTransportType() { + return transportType; + } + + public void setTransportType(String transportType) { + this.transportType = transportType; + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/VifAttachment.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/VifAttachment.java new file mode 100644 index 0000000000..fe7d93adea --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/nicira/VifAttachment.java @@ -0,0 +1,78 @@ +// +// 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.network.nicira; + +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; +import org.apache.commons.lang.builder.ReflectionToStringBuilder; +import org.apache.commons.lang.builder.ToStringStyle; + +public class VifAttachment extends Attachment { + private final String type = "VifAttachment"; + private String vifUuid; + + public VifAttachment() { + } + + public VifAttachment(final String vifUuid) { + this.vifUuid = vifUuid; + } + + public String getVifUuid() { + return vifUuid; + } + + public void setVifUuid(final String vifUuid) { + this.vifUuid = vifUuid; + } + + public String getType() { + return type; + } + + + @Override + public String toString() { + return ReflectionToStringBuilder.reflectionToString(this, ToStringStyle.DEFAULT_STYLE, false); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 31) + .append(this.getClass()) + .append(vifUuid) + .toHashCode(); + } + + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (!(this.getClass().isInstance(obj))) { + return false; + } + final VifAttachment another = (VifAttachment) obj; + return new EqualsBuilder().append(vifUuid, another.vifUuid).isEquals(); + } +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/NiciraNvpRequestWrapper.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/NiciraNvpRequestWrapper.java new file mode 100644 index 0000000000..7b6cd54217 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/NiciraNvpRequestWrapper.java @@ -0,0 +1,77 @@ +// +// 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.network.resource; + +import java.util.Hashtable; +import java.util.Set; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.RequestWrapper; +import com.cloud.resource.ServerResource; + +import org.reflections.Reflections; + +public class NiciraNvpRequestWrapper extends RequestWrapper { + + private static NiciraNvpRequestWrapper instance; + + static { + instance = new NiciraNvpRequestWrapper(); + } + + Reflections baseWrappers = new Reflections("com.cloud.network.resource.wrapper"); + @SuppressWarnings("rawtypes") + Set> baseSet = baseWrappers.getSubTypesOf(CommandWrapper.class); + + private NiciraNvpRequestWrapper() { + init(); + } + + @SuppressWarnings("rawtypes") + private void init() { + // NiciraNvpResource commands + final Hashtable, CommandWrapper> niciraCommands = processAnnotations(baseSet); + + resources.put(NiciraNvpResource.class, niciraCommands); + } + + public static NiciraNvpRequestWrapper getInstance() { + return instance; + } + + @SuppressWarnings({"rawtypes" }) + @Override + public Answer execute(final Command command, final ServerResource serverResource) { + final Class resourceClass = serverResource.getClass(); + + final Hashtable, CommandWrapper> resourceCommands = retrieveResource(command, resourceClass); + + CommandWrapper commandWrapper = retrieveCommands(command.getClass(), resourceCommands); + + while (commandWrapper == null) { + //Could not find the command in the given resource, will traverse the family tree. + commandWrapper = retryWhenAllFails(command, resourceClass, resourceCommands); + } + + return commandWrapper.execute(command, serverResource); + } +} \ No newline at end of file diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/NiciraNvpResource.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/NiciraNvpResource.java new file mode 100644 index 0000000000..bf33a5d9ba --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/NiciraNvpResource.java @@ -0,0 +1,357 @@ +// +// 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.network.resource; + +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.util.Map; + +import javax.naming.ConfigurationException; + +import com.cloud.agent.IAgentControl; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.PingCommand; +import com.cloud.agent.api.StartupCommand; +import com.cloud.agent.api.StartupNiciraNvpCommand; +import com.cloud.host.Host; +import com.cloud.host.Host.Type; +import com.cloud.network.nicira.ControlClusterStatus; +import com.cloud.network.nicira.ControlClusterStatus.ClusterRoleConfig; +import com.cloud.network.nicira.DestinationNatRule; +import com.cloud.network.nicira.Match; +import com.cloud.network.nicira.NatRule; +import com.cloud.network.nicira.NiciraNvpApi; +import com.cloud.network.nicira.NiciraNvpApiException; +import com.cloud.network.nicira.SourceNatRule; +import com.cloud.network.utils.CommandRetryUtility; +import com.cloud.resource.ServerResource; +import com.cloud.utils.nicira.nvp.plugin.NiciraNvpApiVersion; +import com.cloud.utils.rest.CloudstackRESTException; +import com.cloud.utils.rest.HttpClientHelper; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class NiciraNvpResource implements ServerResource { + + private static final Logger s_logger = LoggerFactory.getLogger(NiciraNvpResource.class); + + public static final int NAME_MAX_LEN = 40; + public static final int NUM_RETRIES = 2; + private static final int MAX_REDIRECTS = 5; + + private String name; + private String guid; + private String zoneId; + + private NiciraNvpApi niciraNvpApi; + private NiciraNvpUtilities niciraNvpUtilities; + private CommandRetryUtility retryUtility; + + protected NiciraNvpApi createNiciraNvpApi(final String host, final String username, final String password) throws CloudstackRESTException { + try { + return NiciraNvpApi.create().host(host).username(username).password(password).httpClient(HttpClientHelper.createHttpClient(MAX_REDIRECTS)).build(); + } catch (final KeyManagementException e) { + throw new CloudstackRESTException("Could not create HTTP client", e); + } catch (final NoSuchAlgorithmException e) { + throw new CloudstackRESTException("Could not create HTTP client", e); + } catch (final KeyStoreException e) { + throw new CloudstackRESTException("Could not create HTTP client", e); + } + } + + @Override + public boolean configure(final String ignoredName, final Map params) throws ConfigurationException { + + name = (String) params.get("name"); + if (name == null) { + throw new ConfigurationException("Unable to find name"); + } + + guid = (String) params.get("guid"); + if (guid == null) { + throw new ConfigurationException("Unable to find the guid"); + } + + zoneId = (String) params.get("zoneId"); + if (zoneId == null) { + throw new ConfigurationException("Unable to find zone"); + } + + final String ip = (String) params.get("ip"); + if (ip == null) { + throw new ConfigurationException("Unable to find IP"); + } + + final String adminuser = (String) params.get("adminuser"); + if (adminuser == null) { + throw new ConfigurationException("Unable to find admin username"); + } + + final String adminpass = (String) params.get("adminpass"); + if (adminpass == null) { + throw new ConfigurationException("Unable to find admin password"); + } + + niciraNvpUtilities = NiciraNvpUtilities.getInstance(); + retryUtility = CommandRetryUtility.getInstance(); + retryUtility.setServerResource(this); + + try { + niciraNvpApi = createNiciraNvpApi(ip, adminuser, adminpass); + } catch (final CloudstackRESTException e) { + throw new ConfigurationException("Could not create a Nicira Nvp API client: " + e.getMessage()); + } + + return true; + } + + public NiciraNvpApi getNiciraNvpApi() { + return niciraNvpApi; + } + + public NiciraNvpUtilities getNiciraNvpUtilities() { + return niciraNvpUtilities; + } + + public CommandRetryUtility getRetryUtility() { + return retryUtility; + } + + @Override + public boolean start() { + return true; + } + + @Override + public boolean stop() { + return true; + } + + @Override + public String getName() { + return name; + } + + @Override + public Type getType() { + // Think up a better name for this Type? + return Host.Type.L2Networking; + } + + @Override + public StartupCommand[] initialize() { + final StartupNiciraNvpCommand sc = new StartupNiciraNvpCommand(); + sc.setGuid(guid); + sc.setName(name); + sc.setDataCenter(zoneId); + sc.setPod(""); + sc.setPrivateIpAddress(""); + sc.setStorageIpAddress(""); + sc.setVersion(NiciraNvpResource.class.getPackage().getImplementationVersion()); + return new StartupCommand[] { sc }; + } + + @Override + public PingCommand getCurrentStatus(final long id) { + try { + final ControlClusterStatus ccs = niciraNvpApi.getControlClusterStatus(); + getApiProviderMajorityVersion(ccs); + if (!"stable".equals(ccs.getClusterStatus())) { + s_logger.error("ControlCluster state is not stable: " + ccs.getClusterStatus()); + return null; + } + } catch (final NiciraNvpApiException e) { + s_logger.error("getControlClusterStatus failed", e); + return null; + } + return new PingCommand(Host.Type.L2Networking, id); + } + + private void getApiProviderMajorityVersion(ControlClusterStatus ccs) { + ClusterRoleConfig[] configuredRoles = ccs.getConfiguredRoles(); + if (configuredRoles != null){ + String apiProviderMajorityVersion = searchApiProvider(configuredRoles); + NiciraNvpApiVersion.setNiciraApiVersion(apiProviderMajorityVersion); + NiciraNvpApiVersion.logNiciraApiVersion(); + } + } + + private String searchApiProvider(ClusterRoleConfig[] configuredRoles) { + for (int i = 0; i < configuredRoles.length; i++) { + if (configuredRoles[i].getRole() != null && configuredRoles[i].getRole().equals("api_provider")){ + return configuredRoles[i].getMajorityVersion(); + } + } + return null; + } + + @Override + public Answer executeRequest(final Command cmd) { + final NiciraNvpRequestWrapper wrapper = NiciraNvpRequestWrapper.getInstance(); + try { + return wrapper.execute(cmd, this); + } catch (final Exception e) { + s_logger.debug("Received unsupported command " + cmd.toString()); + return Answer.createUnsupportedCommandAnswer(cmd); + } + } + + @Override + public void disconnected() { + } + + @Override + public IAgentControl getAgentControl() { + return null; + } + + @Override + public void setAgentControl(final IAgentControl agentControl) { + } + + public String natRuleToString(final NatRule rule) { + + final StringBuilder natRuleStr = new StringBuilder(); + natRuleStr.append("Rule "); + natRuleStr.append(rule.getUuid()); + natRuleStr.append(" ("); + natRuleStr.append(rule.getType()); + natRuleStr.append(") :"); + final Match m = rule.getMatch(); + natRuleStr.append("match ("); + natRuleStr.append(m.getProtocol()); + natRuleStr.append(" "); + natRuleStr.append(m.getSourceIpAddresses()); + natRuleStr.append(" ["); + natRuleStr.append(m.getSourcePort()); + natRuleStr.append(" ] -> "); + natRuleStr.append(m.getDestinationIpAddresses()); + natRuleStr.append(" ["); + natRuleStr.append(m.getDestinationPort()); + natRuleStr.append(" ]) -->"); + if ("SourceNatRule".equals(rule.getType())) { + natRuleStr.append(((SourceNatRule) rule).getToSourceIpAddressMin()); + natRuleStr.append("-"); + natRuleStr.append(((SourceNatRule) rule).getToSourceIpAddressMax()); + natRuleStr.append(" ["); + natRuleStr.append(((SourceNatRule) rule).getToSourcePort()); + natRuleStr.append(" ])"); + } else { + natRuleStr.append(((DestinationNatRule) rule).getToDestinationIpAddress()); + natRuleStr.append(" ["); + natRuleStr.append(((DestinationNatRule) rule).getToDestinationPort()); + natRuleStr.append(" ])"); + } + return natRuleStr.toString(); + } + + public String truncate(final String string, final int length) { + if (string.length() <= length) { + return string; + } else { + return string.substring(0, length); + } + } + + public NatRule[] generateStaticNatRulePair(final String insideIp, final String outsideIp) { + final NatRule[] rulepair = new NatRule[2]; + rulepair[0] = new DestinationNatRule(); + rulepair[0].setType("DestinationNatRule"); + rulepair[0].setOrder(100); + rulepair[1] = new SourceNatRule(); + rulepair[1].setType("SourceNatRule"); + rulepair[1].setOrder(100); + + Match m = new Match(); + m.setDestinationIpAddresses(outsideIp); + rulepair[0].setMatch(m); + ((DestinationNatRule) rulepair[0]).setToDestinationIpAddress(insideIp); + + // create matching snat rule + m = new Match(); + m.setSourceIpAddresses(insideIp); + rulepair[1].setMatch(m); + ((SourceNatRule) rulepair[1]).setToSourceIpAddressMin(outsideIp); + ((SourceNatRule) rulepair[1]).setToSourceIpAddressMax(outsideIp); + + return rulepair; + + } + + public NatRule[] generatePortForwardingRulePair(final String insideIp, final int[] insidePorts, final String outsideIp, final int[] outsidePorts, + final String protocol) { + // Start with a basic static nat rule, then add port and protocol details + final NatRule[] rulepair = generateStaticNatRulePair(insideIp, outsideIp); + + ((DestinationNatRule) rulepair[0]).setToDestinationPort(insidePorts[0]); + rulepair[0].getMatch().setDestinationPort(outsidePorts[0]); + rulepair[0].setOrder(50); + rulepair[0].getMatch().setEthertype("IPv4"); + if ("tcp".equals(protocol)) { + rulepair[0].getMatch().setProtocol(6); + } else if ("udp".equals(protocol)) { + rulepair[0].getMatch().setProtocol(17); + } + + ((SourceNatRule) rulepair[1]).setToSourcePort(outsidePorts[0]); + rulepair[1].getMatch().setSourcePort(insidePorts[0]); + rulepair[1].setOrder(50); + rulepair[1].getMatch().setEthertype("IPv4"); + if ("tcp".equals(protocol)) { + rulepair[1].getMatch().setProtocol(6); + } else if ("udp".equals(protocol)) { + rulepair[1].getMatch().setProtocol(17); + } + + return rulepair; + + } + + @Override + public void setName(final String name) { + // TODO Auto-generated method stub + } + + @Override + public void setConfigParams(final Map params) { + // TODO Auto-generated method stub + } + + @Override + public Map getConfigParams() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getRunLevel() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void setRunLevel(final int level) { + // TODO Auto-generated method stub + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/NiciraNvpUtilities.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/NiciraNvpUtilities.java new file mode 100644 index 0000000000..4edf5c35ee --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/NiciraNvpUtilities.java @@ -0,0 +1,65 @@ +// +// 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.network.resource; + +import java.util.ArrayList; +import java.util.List; + +import com.cloud.agent.api.CreateLogicalSwitchPortCommand; +import com.cloud.network.nicira.LogicalSwitch; +import com.cloud.network.nicira.LogicalSwitchPort; +import com.cloud.network.nicira.NiciraNvpTag; +import com.cloud.network.nicira.VifAttachment; + +public class NiciraNvpUtilities { + + private static NiciraNvpUtilities instance; + + static { + instance = new NiciraNvpUtilities(); + } + + private NiciraNvpUtilities() { + } + + public static NiciraNvpUtilities getInstance() { + return instance; + } + + public LogicalSwitch createLogicalSwitch() { + final LogicalSwitch logicalSwitch = new LogicalSwitch(); + return logicalSwitch; + } + + public LogicalSwitchPort createLogicalSwitchPort(final CreateLogicalSwitchPortCommand command) { + final String attachmentUuid = command.getAttachmentUuid(); + + // Tags set to scope cs_account and account name + final List tags = new ArrayList(); + tags.add(new NiciraNvpTag("cs_account", command.getOwnerName())); + + final LogicalSwitchPort logicalSwitchPort = new LogicalSwitchPort(attachmentUuid, tags, true); + return logicalSwitchPort; + } + + public VifAttachment createVifAttachment(final String attachmentUuid) { + return new VifAttachment(attachmentUuid); + } +} \ No newline at end of file diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraCheckHealthCommandWrapper.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraCheckHealthCommandWrapper.java new file mode 100644 index 0000000000..9ce3406e3a --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraCheckHealthCommandWrapper.java @@ -0,0 +1,64 @@ +// +// 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.network.resource.wrapper; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.CheckHealthAnswer; +import com.cloud.agent.api.CheckHealthCommand; +import com.cloud.network.nicira.ControlClusterStatus; +import com.cloud.network.nicira.NiciraNvpApi; +import com.cloud.network.nicira.NiciraNvpApiException; +import com.cloud.network.resource.NiciraNvpResource; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@ResourceWrapper(handles = CheckHealthCommand.class) +public class NiciraCheckHealthCommandWrapper extends CommandWrapper { + + private static final String CONTROL_CLUSTER_STATUS_IS_STABLE = "stable"; + private static final Logger s_logger = LoggerFactory.getLogger(NiciraCheckHealthCommandWrapper.class); + + @Override + public Answer execute(final CheckHealthCommand command, final NiciraNvpResource serverResource) { + final NiciraNvpApi niciraNvpApi = serverResource.getNiciraNvpApi(); + boolean healthy = true; + try { + final ControlClusterStatus clusterStatus = niciraNvpApi.getControlClusterStatus(); + final String status = clusterStatus.getClusterStatus(); + if (clusterIsUnstable(status)) { + s_logger.warn("Control cluster is not stable. Current status is " + status); + healthy = false; + } + } catch (final NiciraNvpApiException e) { + s_logger.error("Exception caught while checking control cluster status during health check", e); + healthy = false; + } + + return new CheckHealthAnswer(command, healthy); + } + + protected boolean clusterIsUnstable(final String clusterStatus) { + return !CONTROL_CLUSTER_STATUS_IS_STABLE.equals(clusterStatus); + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpConfigurePortForwardingRulesCommandWrapper.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpConfigurePortForwardingRulesCommandWrapper.java new file mode 100644 index 0000000000..cb1cf604b5 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpConfigurePortForwardingRulesCommandWrapper.java @@ -0,0 +1,123 @@ +// +// 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.network.resource.wrapper; + +import static com.cloud.network.resource.NiciraNvpResource.NUM_RETRIES; + +import java.util.List; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.ConfigurePortForwardingRulesOnLogicalRouterAnswer; +import com.cloud.agent.api.ConfigurePortForwardingRulesOnLogicalRouterCommand; +import com.cloud.agent.api.to.PortForwardingRuleTO; +import com.cloud.network.nicira.NatRule; +import com.cloud.network.nicira.NiciraNvpApi; +import com.cloud.network.nicira.NiciraNvpApiException; +import com.cloud.network.resource.NiciraNvpResource; +import com.cloud.network.utils.CommandRetryUtility; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@ResourceWrapper(handles = ConfigurePortForwardingRulesOnLogicalRouterCommand.class) +public final class NiciraNvpConfigurePortForwardingRulesCommandWrapper extends CommandWrapper { + + private static final Logger s_logger = LoggerFactory.getLogger(NiciraNvpConfigurePortForwardingRulesCommandWrapper.class); + + @Override + public Answer execute(final ConfigurePortForwardingRulesOnLogicalRouterCommand command, final NiciraNvpResource niciraNvpResource) { + final NiciraNvpApi niciraNvpApi = niciraNvpResource.getNiciraNvpApi(); + try { + final List existingRules = niciraNvpApi.findNatRulesByLogicalRouterUuid(command.getLogicalRouterUuid()); + // Rules of the game (also known as assumptions-that-will-make-stuff-break-later-on) + // A SourceNat rule with a match other than a /32 cidr is assumed to be the "main" SourceNat rule + // Any other SourceNat rule should have a corresponding DestinationNat rule + + for (final PortForwardingRuleTO rule : command.getRules()) { + if (rule.isAlreadyAdded() && !rule.revoked()) { + // Don't need to do anything + continue; + } + + if (rule.getDstPortRange()[0] != rule.getDstPortRange()[1] || rule.getSrcPortRange()[0] != rule.getSrcPortRange()[1]) { + return new ConfigurePortForwardingRulesOnLogicalRouterAnswer(command, false, "Nicira NVP doesn't support port ranges for port forwarding"); + } + + final NatRule[] rulepair = niciraNvpResource.generatePortForwardingRulePair(rule.getDstIp(), rule.getDstPortRange(), rule.getSrcIp(), rule.getSrcPortRange(), + rule.getProtocol()); + + NatRule incoming = null; + NatRule outgoing = null; + + for (final NatRule storedRule : existingRules) { + if (storedRule.equalsIgnoreUuid(rulepair[1])) { + // The outgoing rule exists + outgoing = storedRule; + s_logger.debug("Found matching outgoing rule " + outgoing.getUuid()); + if (incoming != null) { + break; + } + } else if (storedRule.equalsIgnoreUuid(rulepair[0])) { + // The incoming rule exists + incoming = storedRule; + s_logger.debug("Found matching incoming rule " + incoming.getUuid()); + if (outgoing != null) { + break; + } + } + } + if (incoming != null && outgoing != null) { + if (rule.revoked()) { + s_logger.debug("Deleting incoming rule " + incoming.getUuid()); + niciraNvpApi.deleteLogicalRouterNatRule(command.getLogicalRouterUuid(), incoming.getUuid()); + + s_logger.debug("Deleting outgoing rule " + outgoing.getUuid()); + niciraNvpApi.deleteLogicalRouterNatRule(command.getLogicalRouterUuid(), outgoing.getUuid()); + } + } else { + if (rule.revoked()) { + s_logger.warn("Tried deleting a rule that does not exist, " + rule.getSrcIp() + " -> " + rule.getDstIp()); + break; + } + + rulepair[0] = niciraNvpApi.createLogicalRouterNatRule(command.getLogicalRouterUuid(), rulepair[0]); + s_logger.debug("Created " + niciraNvpResource.natRuleToString(rulepair[0])); + + try { + rulepair[1] = niciraNvpApi.createLogicalRouterNatRule(command.getLogicalRouterUuid(), rulepair[1]); + s_logger.debug("Created " + niciraNvpResource.natRuleToString(rulepair[1])); + } catch (final NiciraNvpApiException ex) { + s_logger.warn("NiciraNvpApiException during create call, rolling back previous create"); + niciraNvpApi.deleteLogicalRouterNatRule(command.getLogicalRouterUuid(), rulepair[0].getUuid()); + throw ex; // Rethrow the original exception + } + + } + } + return new ConfigurePortForwardingRulesOnLogicalRouterAnswer(command, true, command.getRules().size() + " PortForwarding rules applied"); + } catch (final NiciraNvpApiException e) { + final CommandRetryUtility retryUtility = niciraNvpResource.getRetryUtility(); + retryUtility.addRetry(command, NUM_RETRIES); + return retryUtility.retry(command, ConfigurePortForwardingRulesOnLogicalRouterAnswer.class, e); + } + } +} \ No newline at end of file diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpConfigurePublicIpsCommandWrapper.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpConfigurePublicIpsCommandWrapper.java new file mode 100644 index 0000000000..d584629a5e --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpConfigurePublicIpsCommandWrapper.java @@ -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. +// + +package com.cloud.network.resource.wrapper; + +import static com.cloud.network.resource.NiciraNvpResource.NUM_RETRIES; + +import java.util.List; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.ConfigurePublicIpsOnLogicalRouterAnswer; +import com.cloud.agent.api.ConfigurePublicIpsOnLogicalRouterCommand; +import com.cloud.network.nicira.LogicalRouterPort; +import com.cloud.network.nicira.NiciraNvpApi; +import com.cloud.network.nicira.NiciraNvpApiException; +import com.cloud.network.resource.NiciraNvpResource; +import com.cloud.network.utils.CommandRetryUtility; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; + +@ResourceWrapper(handles = ConfigurePublicIpsOnLogicalRouterCommand.class) +public final class NiciraNvpConfigurePublicIpsCommandWrapper extends CommandWrapper { + + @Override + public Answer execute(final ConfigurePublicIpsOnLogicalRouterCommand command, final NiciraNvpResource niciraNvpResource) { + final NiciraNvpApi niciraNvpApi = niciraNvpResource.getNiciraNvpApi(); + + try { + final List ports = niciraNvpApi.findLogicalRouterPortByGatewayServiceUuid(command.getLogicalRouterUuid(), command.getL3GatewayServiceUuid()); + if (ports.size() != 1) { + return new ConfigurePublicIpsOnLogicalRouterAnswer(command, false, "No logical router ports found, unable to set ip addresses"); + } + final LogicalRouterPort lrp = ports.get(0); + lrp.setIpAddresses(command.getPublicCidrs()); + niciraNvpApi.updateLogicalRouterPort(command.getLogicalRouterUuid(), lrp); + + return new ConfigurePublicIpsOnLogicalRouterAnswer(command, true, "Configured " + command.getPublicCidrs().size() + " ip addresses on logical router uuid " + + command.getLogicalRouterUuid()); + } catch (final NiciraNvpApiException e) { + final CommandRetryUtility retryUtility = niciraNvpResource.getRetryUtility(); + retryUtility.addRetry(command, NUM_RETRIES); + return retryUtility.retry(command, ConfigurePublicIpsOnLogicalRouterAnswer.class, e); + } + } +} \ No newline at end of file diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpConfigureStaticNatRulesCommandWrapper.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpConfigureStaticNatRulesCommandWrapper.java new file mode 100644 index 0000000000..b23025ceff --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpConfigureStaticNatRulesCommandWrapper.java @@ -0,0 +1,115 @@ +// +// 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.network.resource.wrapper; + +import static com.cloud.network.resource.NiciraNvpResource.NUM_RETRIES; + +import java.util.List; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.ConfigureStaticNatRulesOnLogicalRouterAnswer; +import com.cloud.agent.api.ConfigureStaticNatRulesOnLogicalRouterCommand; +import com.cloud.agent.api.to.StaticNatRuleTO; +import com.cloud.network.nicira.NatRule; +import com.cloud.network.nicira.NiciraNvpApi; +import com.cloud.network.nicira.NiciraNvpApiException; +import com.cloud.network.resource.NiciraNvpResource; +import com.cloud.network.utils.CommandRetryUtility; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@ResourceWrapper(handles = ConfigureStaticNatRulesOnLogicalRouterCommand.class) +public final class NiciraNvpConfigureStaticNatRulesCommandWrapper extends CommandWrapper { + + private static final Logger s_logger = LoggerFactory.getLogger(NiciraNvpConfigureStaticNatRulesCommandWrapper.class); + + @Override + public Answer execute(final ConfigureStaticNatRulesOnLogicalRouterCommand command, final NiciraNvpResource niciraNvpResource) { + final NiciraNvpApi niciraNvpApi = niciraNvpResource.getNiciraNvpApi(); + + try { + final List existingRules = niciraNvpApi.findNatRulesByLogicalRouterUuid(command.getLogicalRouterUuid()); + // Rules of the game (also known as assumptions-that-will-make-stuff-break-later-on) + // A SourceNat rule with a match other than a /32 cidr is assumed to be the "main" SourceNat rule + // Any other SourceNat rule should have a corresponding DestinationNat rule + + for (final StaticNatRuleTO rule : command.getRules()) { + + final NatRule[] rulepair = niciraNvpResource.generateStaticNatRulePair(rule.getDstIp(), rule.getSrcIp()); + + NatRule incoming = null; + NatRule outgoing = null; + + for (final NatRule storedRule : existingRules) { + if (storedRule.equalsIgnoreUuid(rulepair[1])) { + // The outgoing rule exists + outgoing = storedRule; + s_logger.debug("Found matching outgoing rule " + outgoing.getUuid()); + if (incoming != null) { + break; + } + } else if (storedRule.equalsIgnoreUuid(rulepair[0])) { + // The incoming rule exists + incoming = storedRule; + s_logger.debug("Found matching incoming rule " + incoming.getUuid()); + if (outgoing != null) { + break; + } + } + } + if (incoming != null && outgoing != null) { + if (rule.revoked()) { + s_logger.debug("Deleting incoming rule " + incoming.getUuid()); + niciraNvpApi.deleteLogicalRouterNatRule(command.getLogicalRouterUuid(), incoming.getUuid()); + + s_logger.debug("Deleting outgoing rule " + outgoing.getUuid()); + niciraNvpApi.deleteLogicalRouterNatRule(command.getLogicalRouterUuid(), outgoing.getUuid()); + } + } else { + if (rule.revoked()) { + s_logger.warn("Tried deleting a rule that does not exist, " + rule.getSrcIp() + " -> " + rule.getDstIp()); + break; + } + + rulepair[0] = niciraNvpApi.createLogicalRouterNatRule(command.getLogicalRouterUuid(), rulepair[0]); + s_logger.debug("Created " + niciraNvpResource.natRuleToString(rulepair[0])); + + try { + rulepair[1] = niciraNvpApi.createLogicalRouterNatRule(command.getLogicalRouterUuid(), rulepair[1]); + s_logger.debug("Created " + niciraNvpResource.natRuleToString(rulepair[1])); + } catch (final NiciraNvpApiException ex) { + s_logger.debug("Failed to create SourceNatRule, rolling back DestinationNatRule"); + niciraNvpApi.deleteLogicalRouterNatRule(command.getLogicalRouterUuid(), rulepair[0].getUuid()); + throw ex; // Rethrow original exception + } + + } + } + return new ConfigureStaticNatRulesOnLogicalRouterAnswer(command, true, command.getRules().size() + " StaticNat rules applied"); + } catch (final NiciraNvpApiException e) { + final CommandRetryUtility retryUtility = niciraNvpResource.getRetryUtility(); + retryUtility.addRetry(command, NUM_RETRIES); + return retryUtility.retry(command, ConfigureStaticNatRulesOnLogicalRouterAnswer.class, e); + } + } +} \ No newline at end of file diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpCreateLogicalRouterCommandWrapper.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpCreateLogicalRouterCommandWrapper.java new file mode 100644 index 0000000000..9e1e40ce49 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpCreateLogicalRouterCommandWrapper.java @@ -0,0 +1,153 @@ +// +// 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.network.resource.wrapper; + +import static com.cloud.network.resource.NiciraNvpResource.NAME_MAX_LEN; +import static com.cloud.network.resource.NiciraNvpResource.NUM_RETRIES; + +import java.util.ArrayList; +import java.util.List; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.CreateLogicalRouterAnswer; +import com.cloud.agent.api.CreateLogicalRouterCommand; +import com.cloud.network.nicira.L3GatewayAttachment; +import com.cloud.network.nicira.LogicalRouter; +import com.cloud.network.nicira.LogicalRouterPort; +import com.cloud.network.nicira.LogicalSwitchPort; +import com.cloud.network.nicira.Match; +import com.cloud.network.nicira.NiciraNvpApi; +import com.cloud.network.nicira.NiciraNvpApiException; +import com.cloud.network.nicira.NiciraNvpTag; +import com.cloud.network.nicira.PatchAttachment; +import com.cloud.network.nicira.RouterNextHop; +import com.cloud.network.nicira.SingleDefaultRouteImplicitRoutingConfig; +import com.cloud.network.nicira.SourceNatRule; +import com.cloud.network.resource.NiciraNvpResource; +import com.cloud.network.utils.CommandRetryUtility; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@ResourceWrapper(handles = CreateLogicalRouterCommand.class) +public final class NiciraNvpCreateLogicalRouterCommandWrapper extends CommandWrapper { + + private static final Logger s_logger = LoggerFactory.getLogger(NiciraNvpCreateLogicalRouterCommandWrapper.class); + + @Override + public Answer execute(final CreateLogicalRouterCommand command, final NiciraNvpResource niciraNvpResource) { + final String routerName = command.getName(); + final String gatewayServiceUuid = command.getGatewayServiceUuid(); + final String logicalSwitchUuid = command.getLogicalSwitchUuid(); + + final List tags = new ArrayList(); + tags.add(new NiciraNvpTag("cs_account", command.getOwnerName())); + + final String publicNetworkNextHopIp = command.getPublicNextHop(); + final String publicNetworkIpAddress = command.getPublicIpCidr(); + final String internalNetworkAddress = command.getInternalIpCidr(); + + s_logger.debug("Creating a logical router with external ip " + publicNetworkIpAddress + " and internal ip " + internalNetworkAddress + "on gateway service " + + gatewayServiceUuid); + + final NiciraNvpApi niciraNvpApi = niciraNvpResource.getNiciraNvpApi(); + + try { + // Create the Router + LogicalRouter lrc = new LogicalRouter(); + lrc.setDisplayName(niciraNvpResource.truncate(routerName, NAME_MAX_LEN)); + lrc.setTags(tags); + lrc.setRoutingConfig(new SingleDefaultRouteImplicitRoutingConfig(new RouterNextHop(publicNetworkNextHopIp))); + lrc = niciraNvpApi.createLogicalRouter(lrc); + + // store the switchport for rollback + LogicalSwitchPort lsp = null; + + try { + // Create the outside port for the router + LogicalRouterPort lrpo = new LogicalRouterPort(); + lrpo.setAdminStatusEnabled(true); + lrpo.setDisplayName(niciraNvpResource.truncate(routerName + "-outside-port", NAME_MAX_LEN)); + lrpo.setTags(tags); + final List outsideIpAddresses = new ArrayList(); + outsideIpAddresses.add(publicNetworkIpAddress); + lrpo.setIpAddresses(outsideIpAddresses); + lrpo = niciraNvpApi.createLogicalRouterPort(lrc.getUuid(), lrpo); + + // Attach the outside port to the gateway service on the correct VLAN + final L3GatewayAttachment attachment = new L3GatewayAttachment(gatewayServiceUuid); + if (command.getVlanId() != 0) { + attachment.setVlanId(command.getVlanId()); + } + niciraNvpApi.updateLogicalRouterPortAttachment(lrc.getUuid(), lrpo.getUuid(), attachment); + + // Create the inside port for the router + LogicalRouterPort lrpi = new LogicalRouterPort(); + lrpi.setAdminStatusEnabled(true); + lrpi.setDisplayName(niciraNvpResource.truncate(routerName + "-inside-port", NAME_MAX_LEN)); + lrpi.setTags(tags); + final List insideIpAddresses = new ArrayList(); + insideIpAddresses.add(internalNetworkAddress); + lrpi.setIpAddresses(insideIpAddresses); + lrpi = niciraNvpApi.createLogicalRouterPort(lrc.getUuid(), lrpi); + + // Create the inside port on the lswitch + lsp = new LogicalSwitchPort(niciraNvpResource.truncate(routerName + "-inside-port", NAME_MAX_LEN), tags, true); + lsp = niciraNvpApi.createLogicalSwitchPort(logicalSwitchUuid, lsp); + + // Attach the inside router port to the lswitch port with a PatchAttachment + niciraNvpApi.updateLogicalRouterPortAttachment(lrc.getUuid(), lrpi.getUuid(), new PatchAttachment(lsp.getUuid())); + + // Attach the inside lswitch port to the router with a PatchAttachment + niciraNvpApi.updateLogicalSwitchPortAttachment(logicalSwitchUuid, lsp.getUuid(), new PatchAttachment(lrpi.getUuid())); + + // Setup the source nat rule + final SourceNatRule snr = new SourceNatRule(); + snr.setToSourceIpAddressMin(publicNetworkIpAddress.split("/")[0]); + snr.setToSourceIpAddressMax(publicNetworkIpAddress.split("/")[0]); + final Match match = new Match(); + match.setSourceIpAddresses(internalNetworkAddress); + snr.setMatch(match); + snr.setOrder(200); + niciraNvpApi.createLogicalRouterNatRule(lrc.getUuid(), snr); + } catch (final NiciraNvpApiException e) { + // We need to destroy the router if we already created it + // this will also take care of any router ports and rules + try { + niciraNvpApi.deleteLogicalRouter(lrc.getUuid()); + if (lsp != null) { + niciraNvpApi.deleteLogicalSwitchPort(logicalSwitchUuid, lsp.getUuid()); + } + } catch (final NiciraNvpApiException ex) { + } + + throw e; + } + + return new CreateLogicalRouterAnswer(command, true, "Logical Router created (uuid " + lrc.getUuid() + ")", lrc.getUuid()); + } catch (final NiciraNvpApiException e) { + final CommandRetryUtility retryUtility = niciraNvpResource.getRetryUtility(); + retryUtility.addRetry(command, NUM_RETRIES); + return retryUtility.retry(command, CreateLogicalRouterAnswer.class, e); + } + } +} \ No newline at end of file diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpCreateLogicalSwitchCommandWrapper.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpCreateLogicalSwitchCommandWrapper.java new file mode 100644 index 0000000000..7d66ada893 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpCreateLogicalSwitchCommandWrapper.java @@ -0,0 +1,73 @@ +// +// 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.network.resource.wrapper; + +import static com.cloud.network.resource.NiciraNvpResource.NUM_RETRIES; + +import java.util.ArrayList; +import java.util.List; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.CreateLogicalSwitchAnswer; +import com.cloud.agent.api.CreateLogicalSwitchCommand; +import com.cloud.network.nicira.LogicalSwitch; +import com.cloud.network.nicira.NiciraNvpApi; +import com.cloud.network.nicira.NiciraNvpApiException; +import com.cloud.network.nicira.NiciraNvpTag; +import com.cloud.network.nicira.TransportZoneBinding; +import com.cloud.network.resource.NiciraNvpResource; +import com.cloud.network.resource.NiciraNvpUtilities; +import com.cloud.network.utils.CommandRetryUtility; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; + +@ResourceWrapper(handles = CreateLogicalSwitchCommand.class) +public final class NiciraNvpCreateLogicalSwitchCommandWrapper extends CommandWrapper { + + @Override + public Answer execute(final CreateLogicalSwitchCommand command, final NiciraNvpResource niciraNvpResource) { + final NiciraNvpUtilities niciraNvpUtilities = niciraNvpResource.getNiciraNvpUtilities(); + + LogicalSwitch logicalSwitch = niciraNvpUtilities.createLogicalSwitch(); + logicalSwitch.setDisplayName(niciraNvpResource.truncate("lswitch-" + command.getName(), NiciraNvpResource.NAME_MAX_LEN)); + logicalSwitch.setPortIsolationEnabled(false); + + // Set transport binding + final List ltzb = new ArrayList(); + ltzb.add(new TransportZoneBinding(command.getTransportUuid(), command.getTransportType())); + logicalSwitch.setTransportZones(ltzb); + + // Tags set to scope cs_account and account name + final List tags = new ArrayList(); + tags.add(new NiciraNvpTag("cs_account", command.getOwnerName())); + logicalSwitch.setTags(tags); + + try { + final NiciraNvpApi niciraNvpApi = niciraNvpResource.getNiciraNvpApi(); + logicalSwitch = niciraNvpApi.createLogicalSwitch(logicalSwitch); + final String switchUuid = logicalSwitch.getUuid(); + return new CreateLogicalSwitchAnswer(command, true, "Logicalswitch " + switchUuid + " created", switchUuid); + } catch (final NiciraNvpApiException e) { + final CommandRetryUtility retryUtility = niciraNvpResource.getRetryUtility(); + retryUtility.addRetry(command, NUM_RETRIES); + return retryUtility.retry(command, CreateLogicalSwitchAnswer.class, e); + } + } +} \ No newline at end of file diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpCreateLogicalSwitchPortCommandWrapper.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpCreateLogicalSwitchPortCommandWrapper.java new file mode 100644 index 0000000000..0a622c4140 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpCreateLogicalSwitchPortCommandWrapper.java @@ -0,0 +1,71 @@ +// +// 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.network.resource.wrapper; + +import static com.cloud.network.resource.NiciraNvpResource.NUM_RETRIES; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.CreateLogicalSwitchPortAnswer; +import com.cloud.agent.api.CreateLogicalSwitchPortCommand; +import com.cloud.network.nicira.LogicalSwitchPort; +import com.cloud.network.nicira.NiciraNvpApi; +import com.cloud.network.nicira.NiciraNvpApiException; +import com.cloud.network.nicira.VifAttachment; +import com.cloud.network.resource.NiciraNvpResource; +import com.cloud.network.resource.NiciraNvpUtilities; +import com.cloud.network.utils.CommandRetryUtility; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@ResourceWrapper(handles = CreateLogicalSwitchPortCommand.class) +public final class NiciraNvpCreateLogicalSwitchPortCommandWrapper extends CommandWrapper { + + private static final Logger s_logger = LoggerFactory.getLogger(NiciraNvpCreateLogicalSwitchPortCommandWrapper.class); + + @Override + public Answer execute(final CreateLogicalSwitchPortCommand command, final NiciraNvpResource niciraNvpResource) { + final NiciraNvpUtilities niciraNvpUtilities = niciraNvpResource.getNiciraNvpUtilities(); + + final String logicalSwitchUuid = command.getLogicalSwitchUuid(); + final String attachmentUuid = command.getAttachmentUuid(); + + try { + final NiciraNvpApi niciraNvpApi = niciraNvpResource.getNiciraNvpApi(); + + final LogicalSwitchPort logicalSwitchPort = niciraNvpUtilities.createLogicalSwitchPort(command); + final LogicalSwitchPort newPort = niciraNvpApi.createLogicalSwitchPort(logicalSwitchUuid, logicalSwitchPort); + try { + niciraNvpApi.updateLogicalSwitchPortAttachment(command.getLogicalSwitchUuid(), newPort.getUuid(), new VifAttachment(attachmentUuid)); + } catch (final NiciraNvpApiException ex) { + s_logger.warn("modifyLogicalSwitchPort failed after switchport was created, removing switchport"); + niciraNvpApi.deleteLogicalSwitchPort(command.getLogicalSwitchUuid(), newPort.getUuid()); + throw ex; // Rethrow the original exception + } + return new CreateLogicalSwitchPortAnswer(command, true, "Logical switch port " + newPort.getUuid() + " created", newPort.getUuid()); + } catch (final NiciraNvpApiException e) { + final CommandRetryUtility retryUtility = niciraNvpResource.getRetryUtility(); + retryUtility.addRetry(command, NUM_RETRIES); + return retryUtility.retry(command, CreateLogicalSwitchPortAnswer.class, e); + } + } +} \ No newline at end of file diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpDeleteLogicalRouterCommandWrapper.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpDeleteLogicalRouterCommandWrapper.java new file mode 100644 index 0000000000..43665b2dc0 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpDeleteLogicalRouterCommandWrapper.java @@ -0,0 +1,50 @@ +// +// 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.network.resource.wrapper; + +import static com.cloud.network.resource.NiciraNvpResource.NUM_RETRIES; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.DeleteLogicalRouterAnswer; +import com.cloud.agent.api.DeleteLogicalRouterCommand; +import com.cloud.network.nicira.NiciraNvpApi; +import com.cloud.network.nicira.NiciraNvpApiException; +import com.cloud.network.resource.NiciraNvpResource; +import com.cloud.network.utils.CommandRetryUtility; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; + +@ResourceWrapper(handles = DeleteLogicalRouterCommand.class) +public final class NiciraNvpDeleteLogicalRouterCommandWrapper extends CommandWrapper { + + @Override + public Answer execute(final DeleteLogicalRouterCommand command, final NiciraNvpResource niciraNvpResource) { + final NiciraNvpApi niciraNvpApi = niciraNvpResource.getNiciraNvpApi(); + + try { + niciraNvpApi.deleteLogicalRouter(command.getLogicalRouterUuid()); + return new DeleteLogicalRouterAnswer(command, true, "Logical Router deleted (uuid " + command.getLogicalRouterUuid() + ")"); + } catch (final NiciraNvpApiException e) { + final CommandRetryUtility retryUtility = niciraNvpResource.getRetryUtility(); + retryUtility.addRetry(command, NUM_RETRIES); + return retryUtility.retry(command, DeleteLogicalRouterAnswer.class, e); + } + } +} \ No newline at end of file diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpDeleteLogicalSwitchCommandWrapper.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpDeleteLogicalSwitchCommandWrapper.java new file mode 100644 index 0000000000..f7b8ab9b8d --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpDeleteLogicalSwitchCommandWrapper.java @@ -0,0 +1,49 @@ +// +// 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.network.resource.wrapper; + +import static com.cloud.network.resource.NiciraNvpResource.NUM_RETRIES; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.DeleteLogicalSwitchAnswer; +import com.cloud.agent.api.DeleteLogicalSwitchCommand; +import com.cloud.network.nicira.NiciraNvpApi; +import com.cloud.network.nicira.NiciraNvpApiException; +import com.cloud.network.resource.NiciraNvpResource; +import com.cloud.network.utils.CommandRetryUtility; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; + +@ResourceWrapper(handles = DeleteLogicalSwitchCommand.class) +public final class NiciraNvpDeleteLogicalSwitchCommandWrapper extends CommandWrapper { + + @Override + public Answer execute(final DeleteLogicalSwitchCommand command, final NiciraNvpResource niciraNvpResource) { + try { + final NiciraNvpApi niciraNvpApi = niciraNvpResource.getNiciraNvpApi(); + niciraNvpApi.deleteLogicalSwitch(command.getLogicalSwitchUuid()); + return new DeleteLogicalSwitchAnswer(command, true, "Logicalswitch " + command.getLogicalSwitchUuid() + " deleted"); + } catch (final NiciraNvpApiException e) { + final CommandRetryUtility retryUtility = niciraNvpResource.getRetryUtility(); + retryUtility.addRetry(command, NUM_RETRIES); + return retryUtility.retry(command, DeleteLogicalSwitchAnswer.class, e); + } + } +} \ No newline at end of file diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpDeleteLogicalSwitchPortCommandWrapper.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpDeleteLogicalSwitchPortCommandWrapper.java new file mode 100644 index 0000000000..b8c69f2d81 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpDeleteLogicalSwitchPortCommandWrapper.java @@ -0,0 +1,50 @@ +// +// 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.network.resource.wrapper; + +import static com.cloud.network.resource.NiciraNvpResource.NUM_RETRIES; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.DeleteLogicalSwitchPortAnswer; +import com.cloud.agent.api.DeleteLogicalSwitchPortCommand; +import com.cloud.network.nicira.NiciraNvpApi; +import com.cloud.network.nicira.NiciraNvpApiException; +import com.cloud.network.resource.NiciraNvpResource; +import com.cloud.network.utils.CommandRetryUtility; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; + +@ResourceWrapper(handles = DeleteLogicalSwitchPortCommand.class) +public final class NiciraNvpDeleteLogicalSwitchPortCommandWrapper extends CommandWrapper { + + @Override + public Answer execute(final DeleteLogicalSwitchPortCommand command, final NiciraNvpResource niciraNvpResource) { + final NiciraNvpApi niciraNvpApi = niciraNvpResource.getNiciraNvpApi(); + + try { + niciraNvpApi.deleteLogicalSwitchPort(command.getLogicalSwitchUuid(), command.getLogicalSwitchPortUuid()); + return new DeleteLogicalSwitchPortAnswer(command, true, "Logical switch port " + command.getLogicalSwitchPortUuid() + " deleted"); + } catch (final NiciraNvpApiException e) { + final CommandRetryUtility retryUtility = niciraNvpResource.getRetryUtility(); + retryUtility.addRetry(command, NUM_RETRIES); + return retryUtility.retry(command, DeleteLogicalSwitchPortAnswer.class, e); + } + } +} \ No newline at end of file diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpFindLogicalSwitchCommandWrapper.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpFindLogicalSwitchCommandWrapper.java new file mode 100644 index 0000000000..650455f9e0 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpFindLogicalSwitchCommandWrapper.java @@ -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. +// + +package com.cloud.network.resource.wrapper; + +import static com.cloud.network.resource.NiciraNvpResource.NUM_RETRIES; + +import java.util.List; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.FindLogicalSwitchAnswer; +import com.cloud.agent.api.FindLogicalSwitchCommand; +import com.cloud.network.nicira.LogicalSwitch; +import com.cloud.network.nicira.NiciraNvpApi; +import com.cloud.network.nicira.NiciraNvpApiException; +import com.cloud.network.resource.NiciraNvpResource; +import com.cloud.network.utils.CommandRetryUtility; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; + +@ResourceWrapper(handles = FindLogicalSwitchCommand.class) +public final class NiciraNvpFindLogicalSwitchCommandWrapper extends CommandWrapper { + + @Override + public Answer execute(final FindLogicalSwitchCommand command, final NiciraNvpResource niciraNvpResource) { + final String logicalSwitchUuid = command.getLogicalSwitchUuid(); + + final NiciraNvpApi niciraNvpApi = niciraNvpResource.getNiciraNvpApi(); + + try { + final List switches = niciraNvpApi.findLogicalSwitch(logicalSwitchUuid); + if (switches.size() == 0) { + return new FindLogicalSwitchAnswer(command, false, "Logical switc " + logicalSwitchUuid + " not found", null); + } else { + return new FindLogicalSwitchAnswer(command, true, "Logical switch " + logicalSwitchUuid + " found", logicalSwitchUuid); + } + } catch (final NiciraNvpApiException e) { + final CommandRetryUtility retryUtility = niciraNvpResource.getRetryUtility(); + retryUtility.addRetry(command, NUM_RETRIES); + return retryUtility.retry(command, FindLogicalSwitchAnswer.class, e); + } + } +} \ No newline at end of file diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpFindLogicalSwitchPortCommandWrapper.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpFindLogicalSwitchPortCommandWrapper.java new file mode 100644 index 0000000000..8c11427d94 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpFindLogicalSwitchPortCommandWrapper.java @@ -0,0 +1,60 @@ +// +// 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.network.resource.wrapper; + +import static com.cloud.network.resource.NiciraNvpResource.NUM_RETRIES; + +import java.util.List; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.FindLogicalSwitchPortAnswer; +import com.cloud.agent.api.FindLogicalSwitchPortCommand; +import com.cloud.network.nicira.LogicalSwitchPort; +import com.cloud.network.nicira.NiciraNvpApi; +import com.cloud.network.nicira.NiciraNvpApiException; +import com.cloud.network.resource.NiciraNvpResource; +import com.cloud.network.utils.CommandRetryUtility; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; + +@ResourceWrapper(handles = FindLogicalSwitchPortCommand.class) +public final class NiciraNvpFindLogicalSwitchPortCommandWrapper extends CommandWrapper { + + @Override + public Answer execute(final FindLogicalSwitchPortCommand command, final NiciraNvpResource niciraNvpResource) { + final String logicalSwitchUuid = command.getLogicalSwitchUuid(); + final String logicalSwitchPortUuid = command.getLogicalSwitchPortUuid(); + + final NiciraNvpApi niciraNvpApi = niciraNvpResource.getNiciraNvpApi(); + + try { + final List ports = niciraNvpApi.findLogicalSwitchPortsByUuid(logicalSwitchUuid, logicalSwitchPortUuid); + if (ports.size() == 0) { + return new FindLogicalSwitchPortAnswer(command, false, "Logical switchport " + logicalSwitchPortUuid + " not found", null); + } else { + return new FindLogicalSwitchPortAnswer(command, true, "Logical switchport " + logicalSwitchPortUuid + " found", logicalSwitchPortUuid); + } + } catch (final NiciraNvpApiException e) { + final CommandRetryUtility retryUtility = niciraNvpResource.getRetryUtility(); + retryUtility.addRetry(command, NUM_RETRIES); + return retryUtility.retry(command, FindLogicalSwitchPortAnswer.class, e); + } + } +} \ No newline at end of file diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpMaintainCommandWrapper.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpMaintainCommandWrapper.java new file mode 100644 index 0000000000..3015378069 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpMaintainCommandWrapper.java @@ -0,0 +1,36 @@ +// +// 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.network.resource.wrapper; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.MaintainAnswer; +import com.cloud.agent.api.MaintainCommand; +import com.cloud.network.resource.NiciraNvpResource; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; + +@ResourceWrapper(handles = MaintainCommand.class) +public final class NiciraNvpMaintainCommandWrapper extends CommandWrapper { + + @Override + public Answer execute(final MaintainCommand command, final NiciraNvpResource niciraNvpResource) { + return new MaintainAnswer(command); + } +} \ No newline at end of file diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpReadyCommandWrapper.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpReadyCommandWrapper.java new file mode 100644 index 0000000000..3d0d3f3454 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpReadyCommandWrapper.java @@ -0,0 +1,36 @@ +// +// 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.network.resource.wrapper; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.ReadyAnswer; +import com.cloud.agent.api.ReadyCommand; +import com.cloud.network.resource.NiciraNvpResource; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; + +@ResourceWrapper(handles = ReadyCommand.class) +public final class NiciraNvpReadyCommandWrapper extends CommandWrapper { + + @Override + public Answer execute(final ReadyCommand command, final NiciraNvpResource niciraNvpResource) { + return new ReadyAnswer(command); + } +} \ No newline at end of file diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpUpdateLogicalSwitchPortCommandWrapper.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpUpdateLogicalSwitchPortCommandWrapper.java new file mode 100644 index 0000000000..a0d7dc214f --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/resource/wrapper/NiciraNvpUpdateLogicalSwitchPortCommandWrapper.java @@ -0,0 +1,68 @@ +// +// 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.network.resource.wrapper; + +import static com.cloud.network.resource.NiciraNvpResource.NUM_RETRIES; + +import java.util.ArrayList; +import java.util.List; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.UpdateLogicalSwitchPortAnswer; +import com.cloud.agent.api.UpdateLogicalSwitchPortCommand; +import com.cloud.network.nicira.NiciraNvpApi; +import com.cloud.network.nicira.NiciraNvpApiException; +import com.cloud.network.nicira.NiciraNvpTag; +import com.cloud.network.nicira.VifAttachment; +import com.cloud.network.resource.NiciraNvpResource; +import com.cloud.network.resource.NiciraNvpUtilities; +import com.cloud.network.utils.CommandRetryUtility; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; + +@ResourceWrapper(handles = UpdateLogicalSwitchPortCommand.class) +public final class NiciraNvpUpdateLogicalSwitchPortCommandWrapper extends CommandWrapper { + + @Override + public Answer execute(final UpdateLogicalSwitchPortCommand command, final NiciraNvpResource niciraNvpResource) { + final NiciraNvpUtilities niciraNvpUtilities = niciraNvpResource.getNiciraNvpUtilities(); + + final String logicalSwitchUuid = command.getLogicalSwitchUuid(); + final String logicalSwitchPortUuid = command.getLogicalSwitchPortUuid(); + final String attachmentUuid = command.getAttachmentUuid(); + + final NiciraNvpApi niciraNvpApi = niciraNvpResource.getNiciraNvpApi(); + + try { + // Tags set to scope cs_account and account name + final List tags = new ArrayList(); + tags.add(new NiciraNvpTag("cs_account", command.getOwnerName())); + + final VifAttachment vifAttachment = niciraNvpUtilities.createVifAttachment(attachmentUuid); + + niciraNvpApi.updateLogicalSwitchPortAttachment(logicalSwitchUuid, logicalSwitchPortUuid, vifAttachment); + return new UpdateLogicalSwitchPortAnswer(command, true, "Attachment for " + logicalSwitchPortUuid + " updated", logicalSwitchPortUuid); + } catch (final NiciraNvpApiException e) { + final CommandRetryUtility retryUtility = niciraNvpResource.getRetryUtility(); + retryUtility.addRetry(command, NUM_RETRIES); + return retryUtility.retry(command, UpdateLogicalSwitchPortAnswer.class, e); + } + } +} \ No newline at end of file diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/utils/CommandRetryUtility.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/utils/CommandRetryUtility.java new file mode 100644 index 0000000000..2480d98fc3 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/java/com/cloud/network/utils/CommandRetryUtility.java @@ -0,0 +1,90 @@ +// +// 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.network.utils; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.concurrent.ConcurrentHashMap; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.resource.ServerResource; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CommandRetryUtility { + + private static final Logger s_logger = LoggerFactory.getLogger(CommandRetryUtility.class); + + private static final int ZERO = 0; + private static CommandRetryUtility instance; + + static { + instance = new CommandRetryUtility(); + } + + private final ConcurrentHashMap commandsToRetry; + private ServerResource serverResource; + + private CommandRetryUtility() { + commandsToRetry = new ConcurrentHashMap(); + } + + public static CommandRetryUtility getInstance() { + return instance; + } + + public void setServerResource(final ServerResource serverResource) { + this.serverResource = serverResource; + } + + public boolean addRetry(final Command command, final int retries) { + if (commandsToRetry.containsKey(command)) { + // A retry already exists for this command, do not add it again. + // Once all retries are executed, the command will be removed from the map. + return false; + } + commandsToRetry.put(command, retries); + return true; + } + + public Answer retry(final Command command, final Class answerClass, final Exception error) { + if (commandsToRetry.containsKey(command)) { + Integer numRetries = commandsToRetry.get(command); + + if (numRetries > ZERO) { + commandsToRetry.put(command, --numRetries); + + s_logger.warn("Retrying " + command.getClass().getSimpleName() + ". Number of retries remaining: " + numRetries); + + return serverResource.executeRequest(command); + } else { + commandsToRetry.remove(command); + } + } + try { + final Constructor answerConstructor = answerClass.getConstructor(Command.class, Exception.class); + return answerConstructor.newInstance(command, error); + } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + return Answer.createUnsupportedCommandAnswer(command); + } + } +} \ No newline at end of file diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/resources/META-INF/cloudstack/nvp/module.properties b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/resources/META-INF/cloudstack/nvp/module.properties new file mode 100644 index 0000000000..5085fcec0c --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/resources/META-INF/cloudstack/nvp/module.properties @@ -0,0 +1,21 @@ +# +# 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. +# + +name=nvp +parent=network \ No newline at end of file diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/main/resources/META-INF/cloudstack/nvp/spring-nvp-context.xml b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/resources/META-INF/cloudstack/nvp/spring-nvp-context.xml new file mode 100644 index 0000000000..cd8a2c9846 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/main/resources/META-INF/cloudstack/nvp/spring-nvp-context.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/element/NiciraNvpElementTest.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/element/NiciraNvpElementTest.java new file mode 100644 index 0000000000..d18047c3ed --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/element/NiciraNvpElementTest.java @@ -0,0 +1,216 @@ +// +// 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.network.element; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.argThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.naming.ConfigurationException; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.ConfigurePublicIpsOnLogicalRouterAnswer; +import com.cloud.agent.api.ConfigurePublicIpsOnLogicalRouterCommand; +import com.cloud.deploy.DeployDestination; +import com.cloud.domain.Domain; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.network.IpAddress; +import com.cloud.network.Network; +import com.cloud.network.Network.GuestType; +import com.cloud.network.Network.Provider; +import com.cloud.network.Network.Service; +import com.cloud.network.NetworkModel; +import com.cloud.network.Networks.BroadcastDomainType; +import com.cloud.network.Networks.TrafficType; +import com.cloud.network.NiciraNvpDeviceVO; +import com.cloud.network.NiciraNvpRouterMappingVO; +import com.cloud.network.PublicIpAddress; +import com.cloud.network.dao.NetworkServiceMapDao; +import com.cloud.network.dao.NiciraNvpDao; +import com.cloud.network.dao.NiciraNvpRouterMappingDao; +import com.cloud.offering.NetworkOffering; +import com.cloud.resource.ResourceManager; +import com.cloud.user.Account; +import com.cloud.utils.net.Ip; +import com.cloud.vm.ReservationContext; + +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentMatcher; + +public class NiciraNvpElementTest { + + private static final long NETWORK_ID = 42L; + NiciraNvpElement element = new NiciraNvpElement(); + NetworkOrchestrationService networkManager = mock(NetworkOrchestrationService.class); + NetworkModel networkModel = mock(NetworkModel.class); + NetworkServiceMapDao ntwkSrvcDao = mock(NetworkServiceMapDao.class); + AgentManager agentManager = mock(AgentManager.class); + HostDao hostDao = mock(HostDao.class); + NiciraNvpDao niciraNvpDao = mock(NiciraNvpDao.class); + NiciraNvpRouterMappingDao niciraNvpRouterMappingDao = mock(NiciraNvpRouterMappingDao.class); + + @Before + public void setUp() throws ConfigurationException { + element.resourceMgr = mock(ResourceManager.class); + element.networkManager = networkManager; + element.ntwkSrvcDao = ntwkSrvcDao; + element.networkModel = networkModel; + element.agentMgr = agentManager; + element.hostDao = hostDao; + element.niciraNvpDao = niciraNvpDao; + element.niciraNvpRouterMappingDao = niciraNvpRouterMappingDao; + + // Standard responses + when(networkModel.isProviderForNetwork(Provider.NiciraNvp, NETWORK_ID)).thenReturn(true); + + element.configure("NiciraNvpTestElement", Collections. emptyMap()); + } + + @Test + public void canHandleTest() { + final Network net = mock(Network.class); + when(net.getBroadcastDomainType()).thenReturn(BroadcastDomainType.Lswitch); + when(net.getId()).thenReturn(NETWORK_ID); + + when(ntwkSrvcDao.canProviderSupportServiceInNetwork(NETWORK_ID, Service.Connectivity, Provider.NiciraNvp)).thenReturn(true); + // Golden path + assertTrue(element.canHandle(net, Service.Connectivity)); + + when(net.getBroadcastDomainType()).thenReturn(BroadcastDomainType.Vlan); + // Only broadcastdomaintype lswitch is supported + assertFalse(element.canHandle(net, Service.Connectivity)); + + when(net.getBroadcastDomainType()).thenReturn(BroadcastDomainType.Lswitch); + when(ntwkSrvcDao.canProviderSupportServiceInNetwork(NETWORK_ID, Service.Connectivity, Provider.NiciraNvp)).thenReturn(false); + // No nvp provider in the network + assertFalse(element.canHandle(net, Service.Connectivity)); + + when(networkModel.isProviderForNetwork(Provider.NiciraNvp, NETWORK_ID)).thenReturn(false); + when(ntwkSrvcDao.canProviderSupportServiceInNetwork(NETWORK_ID, Service.Connectivity, Provider.NiciraNvp)).thenReturn(true); + // NVP provider does not provide Connectivity for this network + assertFalse(element.canHandle(net, Service.Connectivity)); + + when(networkModel.isProviderForNetwork(Provider.NiciraNvp, NETWORK_ID)).thenReturn(true); + // Only service Connectivity is supported + assertFalse(element.canHandle(net, Service.Dhcp)); + + } + + @Test + public void implementTest() throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { + final Network network = mock(Network.class); + when(network.getBroadcastDomainType()).thenReturn(BroadcastDomainType.Lswitch); + when(network.getId()).thenReturn(NETWORK_ID); + + final NetworkOffering offering = mock(NetworkOffering.class); + when(offering.getId()).thenReturn(NETWORK_ID); + when(offering.getTrafficType()).thenReturn(TrafficType.Guest); + when(offering.getGuestType()).thenReturn(GuestType.Isolated); + + mock(DeployDestination.class); + + final Domain dom = mock(Domain.class); + when(dom.getName()).thenReturn("domain"); + final Account acc = mock(Account.class); + when(acc.getAccountName()).thenReturn("accountname"); + final ReservationContext context = mock(ReservationContext.class); + when(context.getDomain()).thenReturn(dom); + when(context.getAccount()).thenReturn(acc); + } + + @Test + public void applyIpTest() throws ResourceUnavailableException { + final Network network = mock(Network.class); + when(network.getBroadcastDomainType()).thenReturn(BroadcastDomainType.Lswitch); + when(network.getId()).thenReturn(NETWORK_ID); + when(network.getPhysicalNetworkId()).thenReturn(NETWORK_ID); + + final NetworkOffering offering = mock(NetworkOffering.class); + when(offering.getId()).thenReturn(NETWORK_ID); + when(offering.getTrafficType()).thenReturn(TrafficType.Guest); + when(offering.getGuestType()).thenReturn(GuestType.Isolated); + + final List ipAddresses = new ArrayList(); + final PublicIpAddress pipReleased = mock(PublicIpAddress.class); + final PublicIpAddress pipAllocated = mock(PublicIpAddress.class); + final Ip ipReleased = new Ip("42.10.10.10"); + final Ip ipAllocated = new Ip("10.10.10.10"); + when(pipAllocated.getState()).thenReturn(IpAddress.State.Allocated); + when(pipAllocated.getAddress()).thenReturn(ipAllocated); + when(pipAllocated.getNetmask()).thenReturn("255.255.255.0"); + when(pipReleased.getState()).thenReturn(IpAddress.State.Releasing); + when(pipReleased.getAddress()).thenReturn(ipReleased); + when(pipReleased.getNetmask()).thenReturn("255.255.255.0"); + ipAddresses.add(pipAllocated); + ipAddresses.add(pipReleased); + + final Set services = new HashSet(); + services.add(Service.SourceNat); + services.add(Service.StaticNat); + services.add(Service.PortForwarding); + + final List deviceList = new ArrayList(); + final NiciraNvpDeviceVO nndVO = mock(NiciraNvpDeviceVO.class); + final NiciraNvpRouterMappingVO nnrmVO = mock(NiciraNvpRouterMappingVO.class); + when(niciraNvpRouterMappingDao.findByNetworkId(NETWORK_ID)).thenReturn(nnrmVO); + when(nnrmVO.getLogicalRouterUuid()).thenReturn("abcde"); + when(nndVO.getHostId()).thenReturn(NETWORK_ID); + final HostVO hvo = mock(HostVO.class); + when(hvo.getId()).thenReturn(NETWORK_ID); + when(hvo.getDetail("l3gatewayserviceuuid")).thenReturn("abcde"); + when(hostDao.findById(NETWORK_ID)).thenReturn(hvo); + deviceList.add(nndVO); + when(niciraNvpDao.listByPhysicalNetwork(NETWORK_ID)).thenReturn(deviceList); + + final ConfigurePublicIpsOnLogicalRouterAnswer answer = mock(ConfigurePublicIpsOnLogicalRouterAnswer.class); + when(answer.getResult()).thenReturn(true); + when(agentManager.easySend(eq(NETWORK_ID), any(ConfigurePublicIpsOnLogicalRouterCommand.class))).thenReturn(answer); + + assertTrue(element.applyIps(network, ipAddresses, services)); + + verify(agentManager, atLeast(1)).easySend(eq(NETWORK_ID), argThat(new ArgumentMatcher() { + @Override + public boolean matches(final Object argument) { + final ConfigurePublicIpsOnLogicalRouterCommand command = (ConfigurePublicIpsOnLogicalRouterCommand)argument; + if (command.getPublicCidrs().size() == 1) + return true; + return false; + } + })); + } +} \ No newline at end of file diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/guru/NiciraNvpGuestNetworkGuruTest.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/guru/NiciraNvpGuestNetworkGuruTest.java new file mode 100644 index 0000000000..989be320fa --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/guru/NiciraNvpGuestNetworkGuruTest.java @@ -0,0 +1,475 @@ +// +// 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.network.guru; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.Collections; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.CreateLogicalSwitchAnswer; +import com.cloud.agent.api.DeleteLogicalSwitchAnswer; +import com.cloud.dc.DataCenter; +import com.cloud.dc.DataCenter.NetworkType; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.deploy.DeployDestination; +import com.cloud.deploy.DeploymentPlan; +import com.cloud.domain.Domain; +import com.cloud.exception.InsufficientVirtualNetworkCapacityException; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.network.Network; +import com.cloud.network.Network.GuestType; +import com.cloud.network.Network.Service; +import com.cloud.network.Network.State; +import com.cloud.network.NetworkModel; +import com.cloud.network.NetworkProfile; +import com.cloud.network.Networks.BroadcastDomainType; +import com.cloud.network.Networks.TrafficType; +import com.cloud.network.NiciraNvpDeviceVO; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.dao.NiciraNvpDao; +import com.cloud.network.dao.PhysicalNetworkDao; +import com.cloud.network.dao.PhysicalNetworkVO; +import com.cloud.offering.NetworkOffering; +import com.cloud.offerings.dao.NetworkOfferingServiceMapDao; +import com.cloud.user.Account; +import com.cloud.vm.ReservationContext; + +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.junit.Before; +import org.junit.Test; + +public class NiciraNvpGuestNetworkGuruTest { + private static final long NETWORK_ID = 42L; + PhysicalNetworkDao physnetdao = mock(PhysicalNetworkDao.class); + NiciraNvpDao nvpdao = mock(NiciraNvpDao.class); + DataCenterDao dcdao = mock(DataCenterDao.class); + NetworkOfferingServiceMapDao nosd = mock(NetworkOfferingServiceMapDao.class); + AgentManager agentmgr = mock(AgentManager.class); + NetworkOrchestrationService netmgr = mock(NetworkOrchestrationService.class); + NetworkModel netmodel = mock(NetworkModel.class); + + HostDao hostdao = mock(HostDao.class); + NetworkDao netdao = mock(NetworkDao.class); + NiciraNvpGuestNetworkGuru guru; + + @Before + public void setUp() { + guru = new NiciraNvpGuestNetworkGuru(); + ((GuestNetworkGuru)guru)._physicalNetworkDao = physnetdao; + guru.physicalNetworkDao = physnetdao; + guru.niciraNvpDao = nvpdao; + guru._dcDao = dcdao; + guru.ntwkOfferingSrvcDao = nosd; + guru.networkModel = netmodel; + guru.hostDao = hostdao; + guru.agentMgr = agentmgr; + guru.networkDao = netdao; + + final DataCenterVO dc = mock(DataCenterVO.class); + when(dc.getNetworkType()).thenReturn(NetworkType.Advanced); + when(dc.getGuestNetworkCidr()).thenReturn("10.1.1.1/24"); + + when(dcdao.findById((Long)any())).thenReturn(dc); + } + + @Test + public void testCanHandle() { + final NetworkOffering offering = mock(NetworkOffering.class); + when(offering.getId()).thenReturn(NETWORK_ID); + when(offering.getTrafficType()).thenReturn(TrafficType.Guest); + when(offering.getGuestType()).thenReturn(GuestType.Isolated); + + final PhysicalNetworkVO physnet = mock(PhysicalNetworkVO.class); + when(physnet.getIsolationMethods()).thenReturn(Arrays.asList(new String[] {"STT", "VXLAN"})); + when(physnet.getId()).thenReturn(NETWORK_ID); + + when(nosd.areServicesSupportedByNetworkOffering(NETWORK_ID, Service.Connectivity)).thenReturn(true); + + assertTrue(guru.canHandle(offering, NetworkType.Advanced, physnet) == true); + + // Supported: IsolationMethod == VXLAN + when(physnet.getIsolationMethods()).thenReturn(Arrays.asList(new String[] {"VXLAN"})); + assertTrue(guru.canHandle(offering, NetworkType.Advanced, physnet) == true); + + // Not supported TrafficType != Guest + when(offering.getTrafficType()).thenReturn(TrafficType.Management); + assertFalse(guru.canHandle(offering, NetworkType.Advanced, physnet) == true); + + // Not supported: GuestType Shared + when(offering.getTrafficType()).thenReturn(TrafficType.Guest); + when(offering.getGuestType()).thenReturn(GuestType.Shared); + assertFalse(guru.canHandle(offering, NetworkType.Advanced, physnet) == true); + + // Not supported: Basic networking + when(offering.getGuestType()).thenReturn(GuestType.Isolated); + assertFalse(guru.canHandle(offering, NetworkType.Basic, physnet) == true); + + // Not supported: IsolationMethod != STT, VXLAN + when(physnet.getIsolationMethods()).thenReturn(Arrays.asList(new String[] {"VLAN"})); + assertFalse(guru.canHandle(offering, NetworkType.Advanced, physnet) == true); + + } + + @Test + public void testDesign() { + final PhysicalNetworkVO physnet = mock(PhysicalNetworkVO.class); + when(physnetdao.findById((Long)any())).thenReturn(physnet); + when(physnet.getIsolationMethods()).thenReturn(Arrays.asList(new String[] {"STT", "VXLAN"})); + when(physnet.getId()).thenReturn(NETWORK_ID); + + final NiciraNvpDeviceVO device = mock(NiciraNvpDeviceVO.class); + when(nvpdao.listByPhysicalNetwork(NETWORK_ID)).thenReturn(Arrays.asList(new NiciraNvpDeviceVO[] {device})); + when(device.getId()).thenReturn(1L); + + final NetworkOffering offering = mock(NetworkOffering.class); + when(offering.getId()).thenReturn(NETWORK_ID); + when(offering.getTrafficType()).thenReturn(TrafficType.Guest); + when(offering.getGuestType()).thenReturn(GuestType.Isolated); + + when(nosd.areServicesSupportedByNetworkOffering(NETWORK_ID, Service.Connectivity)).thenReturn(true); + + final DeploymentPlan plan = mock(DeploymentPlan.class); + final Network network = mock(Network.class); + final Account account = mock(Account.class); + + final Network designednetwork = guru.design(offering, plan, network, account); + assertTrue(designednetwork != null); + assertTrue(designednetwork.getBroadcastDomainType() == BroadcastDomainType.Lswitch); + } + + @Test + public void testDesignNoElementOnPhysicalNetwork() { + final PhysicalNetworkVO physnet = mock(PhysicalNetworkVO.class); + when(physnetdao.findById((Long)any())).thenReturn(physnet); + when(physnet.getIsolationMethods()).thenReturn(Arrays.asList(new String[] {"STT", "VXLAN"})); + when(physnet.getId()).thenReturn(NETWORK_ID); + + mock(NiciraNvpDeviceVO.class); + when(nvpdao.listByPhysicalNetwork(NETWORK_ID)).thenReturn(Collections. emptyList()); + + final NetworkOffering offering = mock(NetworkOffering.class); + when(offering.getId()).thenReturn(NETWORK_ID); + when(offering.getTrafficType()).thenReturn(TrafficType.Guest); + when(offering.getGuestType()).thenReturn(GuestType.Isolated); + + final DeploymentPlan plan = mock(DeploymentPlan.class); + final Network network = mock(Network.class); + final Account account = mock(Account.class); + + final Network designednetwork = guru.design(offering, plan, network, account); + assertTrue(designednetwork == null); + } + + @Test + public void testDesignNoIsolationMethodSTT() { + final PhysicalNetworkVO physnet = mock(PhysicalNetworkVO.class); + when(physnetdao.findById((Long)any())).thenReturn(physnet); + when(physnet.getIsolationMethods()).thenReturn(Arrays.asList(new String[] {"VLAN"})); + when(physnet.getId()).thenReturn(NETWORK_ID); + + mock(NiciraNvpDeviceVO.class); + when(nvpdao.listByPhysicalNetwork(NETWORK_ID)).thenReturn(Collections. emptyList()); + + final NetworkOffering offering = mock(NetworkOffering.class); + when(offering.getId()).thenReturn(NETWORK_ID); + when(offering.getTrafficType()).thenReturn(TrafficType.Guest); + when(offering.getGuestType()).thenReturn(GuestType.Isolated); + + final DeploymentPlan plan = mock(DeploymentPlan.class); + final Network network = mock(Network.class); + final Account account = mock(Account.class); + + final Network designednetwork = guru.design(offering, plan, network, account); + assertTrue(designednetwork == null); + } + + @Test + public void testDesignNoConnectivityInOffering() { + final PhysicalNetworkVO physnet = mock(PhysicalNetworkVO.class); + when(physnetdao.findById((Long)any())).thenReturn(physnet); + when(physnet.getIsolationMethods()).thenReturn(Arrays.asList(new String[] {"STT", "VXLAN"})); + when(physnet.getId()).thenReturn(NETWORK_ID); + + final NiciraNvpDeviceVO device = mock(NiciraNvpDeviceVO.class); + when(nvpdao.listByPhysicalNetwork(NETWORK_ID)).thenReturn(Arrays.asList(new NiciraNvpDeviceVO[] {device})); + when(device.getId()).thenReturn(1L); + + final NetworkOffering offering = mock(NetworkOffering.class); + when(offering.getId()).thenReturn(NETWORK_ID); + when(offering.getTrafficType()).thenReturn(TrafficType.Guest); + when(offering.getGuestType()).thenReturn(GuestType.Isolated); + + when(nosd.areServicesSupportedByNetworkOffering(NETWORK_ID, Service.Connectivity)).thenReturn(false); + + final DeploymentPlan plan = mock(DeploymentPlan.class); + final Network network = mock(Network.class); + final Account account = mock(Account.class); + + final Network designednetwork = guru.design(offering, plan, network, account); + assertTrue(designednetwork == null); + } + + @Test + public void testImplement() throws InsufficientVirtualNetworkCapacityException { + final PhysicalNetworkVO physnet = mock(PhysicalNetworkVO.class); + when(physnetdao.findById((Long)any())).thenReturn(physnet); + when(physnet.getIsolationMethods()).thenReturn(Arrays.asList(new String[] {"STT", "VXLAN"})); + when(physnet.getId()).thenReturn(NETWORK_ID); + + final NiciraNvpDeviceVO device = mock(NiciraNvpDeviceVO.class); + when(nvpdao.listByPhysicalNetwork(NETWORK_ID)).thenReturn(Arrays.asList(new NiciraNvpDeviceVO[] {device})); + when(device.getId()).thenReturn(1L); + + final NetworkOffering offering = mock(NetworkOffering.class); + when(offering.getId()).thenReturn(NETWORK_ID); + when(offering.getTrafficType()).thenReturn(TrafficType.Guest); + when(offering.getGuestType()).thenReturn(GuestType.Isolated); + + when(nosd.areServicesSupportedByNetworkOffering(NETWORK_ID, Service.Connectivity)).thenReturn(false); + + mock(DeploymentPlan.class); + + final NetworkVO network = mock(NetworkVO.class); + when(network.getName()).thenReturn("testnetwork"); + when(network.getState()).thenReturn(State.Implementing); + when(network.getPhysicalNetworkId()).thenReturn(NETWORK_ID); + + final DeployDestination dest = mock(DeployDestination.class); + + final DataCenter dc = mock(DataCenter.class); + when(dest.getDataCenter()).thenReturn(dc); + + final HostVO niciraHost = mock(HostVO.class); + when(hostdao.findById(anyLong())).thenReturn(niciraHost); + when(niciraHost.getDetail("transportzoneuuid")).thenReturn("aaaa"); + when(niciraHost.getDetail("transportzoneisotype")).thenReturn("stt"); + when(niciraHost.getId()).thenReturn(NETWORK_ID); + + when(netmodel.findPhysicalNetworkId(anyLong(), (String)any(), (TrafficType)any())).thenReturn(NETWORK_ID); + final Domain dom = mock(Domain.class); + when(dom.getName()).thenReturn("domain"); + final Account acc = mock(Account.class); + when(acc.getAccountName()).thenReturn("accountname"); + final ReservationContext res = mock(ReservationContext.class); + when(res.getDomain()).thenReturn(dom); + when(res.getAccount()).thenReturn(acc); + + final CreateLogicalSwitchAnswer answer = mock(CreateLogicalSwitchAnswer.class); + when(answer.getResult()).thenReturn(true); + when(answer.getLogicalSwitchUuid()).thenReturn("aaaaa"); + when(agentmgr.easySend(eq(NETWORK_ID), (Command)any())).thenReturn(answer); + + final Network implementednetwork = guru.implement(network, offering, dest, res); + assertTrue(implementednetwork != null); + verify(agentmgr, times(1)).easySend(eq(NETWORK_ID), (Command)any()); + } + + @Test + public void testImplementWithCidr() throws InsufficientVirtualNetworkCapacityException { + final PhysicalNetworkVO physnet = mock(PhysicalNetworkVO.class); + when(physnetdao.findById((Long)any())).thenReturn(physnet); + when(physnet.getIsolationMethods()).thenReturn(Arrays.asList(new String[] {"STT"})); + when(physnet.getId()).thenReturn(NETWORK_ID); + + final NiciraNvpDeviceVO device = mock(NiciraNvpDeviceVO.class); + when(nvpdao.listByPhysicalNetwork(NETWORK_ID)).thenReturn(Arrays.asList(new NiciraNvpDeviceVO[] {device})); + when(device.getId()).thenReturn(1L); + + final NetworkOffering offering = mock(NetworkOffering.class); + when(offering.getId()).thenReturn(NETWORK_ID); + when(offering.getTrafficType()).thenReturn(TrafficType.Guest); + when(offering.getGuestType()).thenReturn(GuestType.Isolated); + + when(nosd.areServicesSupportedByNetworkOffering(NETWORK_ID, Service.Connectivity)).thenReturn(false); + + mock(DeploymentPlan.class); + + final NetworkVO network = mock(NetworkVO.class); + when(network.getName()).thenReturn("testnetwork"); + when(network.getState()).thenReturn(State.Implementing); + when(network.getGateway()).thenReturn("10.1.1.1"); + when(network.getCidr()).thenReturn("10.1.1.0/24"); + when(network.getPhysicalNetworkId()).thenReturn(NETWORK_ID); + + final DeployDestination dest = mock(DeployDestination.class); + + final DataCenter dc = mock(DataCenter.class); + when(dest.getDataCenter()).thenReturn(dc); + + final HostVO niciraHost = mock(HostVO.class); + when(hostdao.findById(anyLong())).thenReturn(niciraHost); + when(niciraHost.getDetail("transportzoneuuid")).thenReturn("aaaa"); + when(niciraHost.getDetail("transportzoneisotype")).thenReturn("stt"); + when(niciraHost.getId()).thenReturn(NETWORK_ID); + + when(netmodel.findPhysicalNetworkId(anyLong(), (String)any(), (TrafficType)any())).thenReturn(NETWORK_ID); + final Domain dom = mock(Domain.class); + when(dom.getName()).thenReturn("domain"); + final Account acc = mock(Account.class); + when(acc.getAccountName()).thenReturn("accountname"); + final ReservationContext res = mock(ReservationContext.class); + when(res.getDomain()).thenReturn(dom); + when(res.getAccount()).thenReturn(acc); + + final CreateLogicalSwitchAnswer answer = mock(CreateLogicalSwitchAnswer.class); + when(answer.getResult()).thenReturn(true); + when(answer.getLogicalSwitchUuid()).thenReturn("aaaaa"); + when(agentmgr.easySend(eq(NETWORK_ID), (Command)any())).thenReturn(answer); + + final Network implementednetwork = guru.implement(network, offering, dest, res); + assertTrue(implementednetwork != null); + assertTrue(implementednetwork.getCidr().equals("10.1.1.0/24")); + assertTrue(implementednetwork.getGateway().equals("10.1.1.1")); + verify(agentmgr, times(1)).easySend(eq(NETWORK_ID), (Command)any()); + } + + @Test + public void testImplementURIException() throws InsufficientVirtualNetworkCapacityException { + final PhysicalNetworkVO physnet = mock(PhysicalNetworkVO.class); + when(physnetdao.findById((Long)any())).thenReturn(physnet); + when(physnet.getIsolationMethods()).thenReturn(Arrays.asList(new String[] {"STT"})); + when(physnet.getId()).thenReturn(NETWORK_ID); + + final NiciraNvpDeviceVO device = mock(NiciraNvpDeviceVO.class); + when(nvpdao.listByPhysicalNetwork(NETWORK_ID)).thenReturn(Arrays.asList(new NiciraNvpDeviceVO[] {device})); + when(device.getId()).thenReturn(1L); + + final NetworkOffering offering = mock(NetworkOffering.class); + when(offering.getId()).thenReturn(NETWORK_ID); + when(offering.getTrafficType()).thenReturn(TrafficType.Guest); + when(offering.getGuestType()).thenReturn(GuestType.Isolated); + + when(nosd.areServicesSupportedByNetworkOffering(NETWORK_ID, Service.Connectivity)).thenReturn(false); + + mock(DeploymentPlan.class); + + final NetworkVO network = mock(NetworkVO.class); + when(network.getName()).thenReturn("testnetwork"); + when(network.getState()).thenReturn(State.Implementing); + when(network.getPhysicalNetworkId()).thenReturn(NETWORK_ID); + + final DeployDestination dest = mock(DeployDestination.class); + + final DataCenter dc = mock(DataCenter.class); + when(dest.getDataCenter()).thenReturn(dc); + + final HostVO niciraHost = mock(HostVO.class); + when(hostdao.findById(anyLong())).thenReturn(niciraHost); + when(niciraHost.getDetail("transportzoneuuid")).thenReturn("aaaa"); + when(niciraHost.getDetail("transportzoneisotype")).thenReturn("stt"); + when(niciraHost.getId()).thenReturn(NETWORK_ID); + + when(netmodel.findPhysicalNetworkId(anyLong(), (String)any(), (TrafficType)any())).thenReturn(NETWORK_ID); + final Domain dom = mock(Domain.class); + when(dom.getName()).thenReturn("domain"); + final Account acc = mock(Account.class); + when(acc.getAccountName()).thenReturn("accountname"); + final ReservationContext res = mock(ReservationContext.class); + when(res.getDomain()).thenReturn(dom); + when(res.getAccount()).thenReturn(acc); + + final CreateLogicalSwitchAnswer answer = mock(CreateLogicalSwitchAnswer.class); + when(answer.getResult()).thenReturn(true); + //when(answer.getLogicalSwitchUuid()).thenReturn("aaaaa"); + when(agentmgr.easySend(eq(NETWORK_ID), (Command)any())).thenReturn(answer); + + final Network implementednetwork = guru.implement(network, offering, dest, res); + assertTrue(implementednetwork == null); + verify(agentmgr, times(1)).easySend(eq(NETWORK_ID), (Command)any()); + } + + @Test + public void testShutdown() throws InsufficientVirtualNetworkCapacityException, URISyntaxException { + final PhysicalNetworkVO physnet = mock(PhysicalNetworkVO.class); + when(physnetdao.findById((Long)any())).thenReturn(physnet); + when(physnet.getIsolationMethods()).thenReturn(Arrays.asList(new String[] {"STT", "VXLAN"})); + when(physnet.getId()).thenReturn(NETWORK_ID); + + final NiciraNvpDeviceVO device = mock(NiciraNvpDeviceVO.class); + when(nvpdao.listByPhysicalNetwork(NETWORK_ID)).thenReturn(Arrays.asList(new NiciraNvpDeviceVO[] {device})); + when(device.getId()).thenReturn(1L); + + final NetworkOffering offering = mock(NetworkOffering.class); + when(offering.getId()).thenReturn(NETWORK_ID); + when(offering.getTrafficType()).thenReturn(TrafficType.Guest); + when(offering.getGuestType()).thenReturn(GuestType.Isolated); + + when(nosd.areServicesSupportedByNetworkOffering(NETWORK_ID, Service.Connectivity)).thenReturn(false); + + mock(DeploymentPlan.class); + + final NetworkVO network = mock(NetworkVO.class); + when(network.getName()).thenReturn("testnetwork"); + when(network.getState()).thenReturn(State.Implementing); + when(network.getBroadcastDomainType()).thenReturn(BroadcastDomainType.Lswitch); + when(network.getBroadcastUri()).thenReturn(new URI("lswitch:aaaaa")); + when(network.getPhysicalNetworkId()).thenReturn(NETWORK_ID); + when(netdao.findById(NETWORK_ID)).thenReturn(network); + + final DeployDestination dest = mock(DeployDestination.class); + + final DataCenter dc = mock(DataCenter.class); + when(dest.getDataCenter()).thenReturn(dc); + + final HostVO niciraHost = mock(HostVO.class); + when(hostdao.findById(anyLong())).thenReturn(niciraHost); + when(niciraHost.getDetail("transportzoneuuid")).thenReturn("aaaa"); + when(niciraHost.getDetail("transportzoneisotype")).thenReturn("stt"); + when(niciraHost.getId()).thenReturn(NETWORK_ID); + + when(netmodel.findPhysicalNetworkId(anyLong(), (String)any(), (TrafficType)any())).thenReturn(NETWORK_ID); + final Domain dom = mock(Domain.class); + when(dom.getName()).thenReturn("domain"); + final Account acc = mock(Account.class); + when(acc.getAccountName()).thenReturn("accountname"); + final ReservationContext res = mock(ReservationContext.class); + when(res.getDomain()).thenReturn(dom); + when(res.getAccount()).thenReturn(acc); + + final DeleteLogicalSwitchAnswer answer = mock(DeleteLogicalSwitchAnswer.class); + when(answer.getResult()).thenReturn(true); + when(agentmgr.easySend(eq(NETWORK_ID), (Command)any())).thenReturn(answer); + + final NetworkProfile implementednetwork = mock(NetworkProfile.class); + when(implementednetwork.getId()).thenReturn(NETWORK_ID); + when(implementednetwork.getBroadcastUri()).thenReturn(new URI("lswitch:aaaa")); + when(offering.getSpecifyVlan()).thenReturn(false); + + guru.shutdown(implementednetwork, offering); + verify(agentmgr, times(1)).easySend(eq(NETWORK_ID), (Command)any()); + verify(implementednetwork, times(1)).setBroadcastUri(null); + } +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/ExecutionCounterTest.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/ExecutionCounterTest.java new file mode 100644 index 0000000000..b14694dc09 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/ExecutionCounterTest.java @@ -0,0 +1,102 @@ +// +// 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.network.nicira; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; + +public class ExecutionCounterTest { + + @Test + public void testIncrementCounter() throws Exception { + final ExecutionCounter executionCounter = new ExecutionCounter(-1); + + executionCounter.incrementExecutionCounter().incrementExecutionCounter(); + + assertThat(executionCounter.getValue(), equalTo(2)); + } + + @Test + public void testHasNotYetReachedTheExecutuionLimit() throws Exception { + final ExecutionCounter executionCounter = new ExecutionCounter(2); + + executionCounter.incrementExecutionCounter(); + + assertThat(executionCounter.hasReachedExecutionLimit(), equalTo(false)); + } + + @Test + public void testHasAlreadyReachedTheExecutuionLimit() throws Exception { + final ExecutionCounter executionCounter = new ExecutionCounter(2); + + executionCounter.incrementExecutionCounter().incrementExecutionCounter(); + + assertThat(executionCounter.hasReachedExecutionLimit(), equalTo(true)); + } + + @Test + public void testConcurrentUpdatesToCounter() throws Exception { + final ExecutionCounter executionCounter = new ExecutionCounter(0); + final ExecutorService executorService = Executors.newFixedThreadPool(3); + final AtomicInteger counterTask1 = new AtomicInteger(-1); + final AtomicInteger counterTask2 = new AtomicInteger(-1); + final AtomicInteger counterTask3 = new AtomicInteger(-1); + + final Runnable task1 = new Runnable() { + @Override + public void run() { + executionCounter.incrementExecutionCounter().incrementExecutionCounter(); + executionCounter.incrementExecutionCounter().incrementExecutionCounter(); + counterTask1.set(executionCounter.getValue()); + } + }; + final Runnable task2 = new Runnable() { + @Override + public void run() { + executionCounter.incrementExecutionCounter().incrementExecutionCounter(); + counterTask2.set(executionCounter.getValue()); + } + }; + final Runnable task3 = new Runnable() { + @Override + public void run() { + counterTask3.set(executionCounter.getValue()); + } + }; + + executorService.execute(task1); + executorService.execute(task2); + executorService.execute(task3); + + executorService.shutdown(); + executorService.awaitTermination(5L, TimeUnit.SECONDS); + + assertThat(counterTask1.get(), equalTo(4)); + assertThat(counterTask2.get(), equalTo(2)); + assertThat(counterTask3.get(), equalTo(0)); + } +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NatRuleAdapterTest.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NatRuleAdapterTest.java new file mode 100644 index 0000000000..f8da5f3a42 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NatRuleAdapterTest.java @@ -0,0 +1,60 @@ +// +// 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.network.nicira; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; + +import org.junit.Test; + +public class NatRuleAdapterTest { + private final Gson gson = new GsonBuilder() + .registerTypeAdapter(NatRule.class, new NatRuleAdapter()) + .create(); + + @Test(expected = JsonParseException.class) + public void testNatRuleAdapterNoType() { + gson.fromJson("{}", NatRule.class); + } + + @Test(expected = JsonParseException.class) + public void testNatRuleAdapterWrongType() { + gson.fromJson("{type : \"WrongType\"}", NatRule.class); + } + + @Test() + public void testNatRuleAdapterWithSourceNatRule() { + final SourceNatRule sourceNatRule = (SourceNatRule) gson.fromJson("{type : \"SourceNatRule\"}", NatRule.class); + + assertThat(sourceNatRule, instanceOf(SourceNatRule.class)); + } + + @Test() + public void testNatRuleAdapterWithDestinationNatRule() { + final DestinationNatRule destinationNatRule = (DestinationNatRule) gson.fromJson("{type : \"DestinationNatRule\"}", NatRule.class); + + assertThat(destinationNatRule, instanceOf(DestinationNatRule.class)); + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NatRuleTest.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NatRuleTest.java new file mode 100644 index 0000000000..487fa33389 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NatRuleTest.java @@ -0,0 +1,55 @@ +// +// 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.network.nicira; + +import static org.junit.Assert.assertTrue; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import org.junit.Test; + +public class NatRuleTest { + + @Test + public void testNatRuleEncoding() { + final Gson gson = + new GsonBuilder().registerTypeAdapter(NatRule.class, new NatRuleAdapter()) + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .create(); + + final DestinationNatRule rn1 = new DestinationNatRule(); + rn1.setToDestinationIpAddress("10.10.10.10"); + rn1.setToDestinationPort(80); + final Match mr1 = new Match(); + mr1.setSourceIpAddresses("11.11.11.11/24"); + mr1.setEthertype("IPv4"); + mr1.setProtocol(6); + rn1.setMatch(mr1); + + final String jsonString = gson.toJson(rn1); + final NatRule dnr = gson.fromJson(jsonString, NatRule.class); + + assertTrue(dnr instanceof DestinationNatRule); + assertTrue(rn1.equals(dnr)); + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NiciraNvpApiIT.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NiciraNvpApiIT.java new file mode 100644 index 0000000000..7bb3f3b2cc --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NiciraNvpApiIT.java @@ -0,0 +1,319 @@ +// +// 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.network.nicira; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import com.cloud.utils.PropertiesUtil; +import com.cloud.utils.rest.HttpClientHelper; + +import org.junit.Before; +import org.junit.Test; + +public class NiciraNvpApiIT { + + protected NiciraNvpApi api; + + protected long timestamp = System.currentTimeMillis(); + + @Before + public void setup() throws Exception { + PropertiesUtil.loadFromFile(PropertiesUtil.findConfigFile("config.properties")); + final String host = System.getProperty("nvp.host"); + final String user = System.getProperty("nvp.admin.user"); + final String pass = System.getProperty("nvp.admin.pwd"); + api = NiciraNvpApi.create() + .host(host) + .username(user) + .password(pass) + .httpClient(HttpClientHelper.createHttpClient(5)) + .build(); + } + + @Test + public void testCRUDSecurityProfile() { + SecurityProfile sProfile = new SecurityProfile(); + sProfile.setDisplayName("SecProfile" + timestamp); + + final List egressRules = new ArrayList(); + sProfile.setLogicalPortEgressRules(egressRules); + egressRules.add(new SecurityRule(SecurityRule.ETHERTYPE_IPV4, "1.10.10.0", null, 80, 88, 6)); + egressRules.add(new SecurityRule(SecurityRule.ETHERTYPE_IPV6, "2a80:34ac::1", null, 90, 98, 6)); + + final List ingressRules = new ArrayList(); + sProfile.setLogicalPortIngressRules(ingressRules); + ingressRules.add(new SecurityRule(SecurityRule.ETHERTYPE_IPV4, "1.10.10.0", null, 50, 58, 6)); + ingressRules.add(new SecurityRule(SecurityRule.ETHERTYPE_IPV6, "280a:3ac4::1", null, 60, 68, 6)); + + final List tags = new ArrayList(); + sProfile.setTags(tags); + tags.add(new NiciraNvpTag("nvp", "MyTag1")); + tags.add(new NiciraNvpTag("nicira", "MyTag2")); + // In the creation we don't get to specify UUID, href or schema: they don't exist yet + + try { + sProfile = api.createSecurityProfile(sProfile); + + // We can now update the new entity + sProfile.setDisplayName("UpdatedSecProfile" + timestamp); + api.updateSecurityProfile(sProfile, sProfile.getUuid()); + + // Read them all + List profiles = api.findSecurityProfile(); + SecurityProfile scInList = null; + for (final SecurityProfile iProfile : profiles) { + if (iProfile.getUuid().equalsIgnoreCase(sProfile.getUuid())) { + scInList = iProfile; + } + } + assertEquals("Read a Security Profile different from the one just created and updated", sProfile, scInList); + + // Read them filtered by uuid (get one) + profiles = api.findSecurityProfile(sProfile.getUuid()); + assertEquals("Read a Security Profile different from the one just created and updated", sProfile, profiles.get(0)); + assertEquals("Read a Security Profile filtered by unique id (UUID) with more than one item", 1, profiles.size()); + + // We can now delete the new entity + api.deleteSecurityProfile(sProfile.getUuid()); + } catch (final NiciraNvpApiException e) { + e.printStackTrace(); + assertTrue("Errors in Security Profile CRUD", false); + } + } + + @Test + public void testCRUDAcl() { + Acl acl = new Acl(); + acl.setDisplayName("Acl" + timestamp); + + // Note that if the protocol is 6 (TCP) then you cannot put ICMP code and type + // Note that if the protocol is 1 (ICMP) then you cannot put ports + final List egressRules = new ArrayList(); + acl.setLogicalPortEgressRules(egressRules); + egressRules.add(new AclRule(AclRule.ETHERTYPE_IPV4, 1, "allow", null, null, "1.10.10.0", "1.10.10.1", null, null, null, null, 0, 0, 5)); + egressRules.add(new AclRule(AclRule.ETHERTYPE_IPV4, 6, "allow", null, null, "1.10.10.6", "1.10.10.7", 80, 80, 80, 80, 1, null, null)); + + final List ingressRules = new ArrayList(); + acl.setLogicalPortIngressRules(ingressRules); + ingressRules.add(new AclRule(AclRule.ETHERTYPE_IPV4, 1, "allow", null, null, "1.10.10.0", "1.10.10.1", null, null, null, null, 0, 0, 5)); + ingressRules.add(new AclRule(AclRule.ETHERTYPE_IPV4, 6, "allow", null, null, "1.10.10.6", "1.10.10.7", 80, 80, 80, 80, 1, null, null)); + + final List tags = new ArrayList(); + acl.setTags(tags); + tags.add(new NiciraNvpTag("nvp", "MyTag1")); + tags.add(new NiciraNvpTag("nicira", "MyTag2")); + // In the creation we don't get to specify UUID, href or schema: they don't exist yet + + try { + acl = api.createAcl(acl); + + // We can now update the new entity + acl.setDisplayName("UpdatedAcl" + timestamp); + api.updateAcl(acl, acl.getUuid()); + + // Read them all + List acls = api.findAcl(); + Acl scInList = null; + for (final Acl iAcl : acls) { + if (iAcl.getUuid().equalsIgnoreCase(acl.getUuid())) { + scInList = iAcl; + } + } + assertEquals("Read a ACL different from the one just created and updated", acl, scInList); + + // Read them filtered by uuid (get one) + acls = api.findAcl(acl.getUuid()); + assertEquals("Read a ACL different from the one just created and updated", acl, acls.get(0)); + assertEquals("Read a ACL filtered by unique id (UUID) with more than one item", 1, acls.size()); + + // We can now delete the new entity + api.deleteAcl(acl.getUuid()); + } catch (final NiciraNvpApiException e) { + e.printStackTrace(); + assertTrue("Errors in ACL CRUD", false); + } + } + + @Test + public void testCRUDLogicalSwitch() throws Exception { + LogicalSwitch logicalSwitch = new LogicalSwitch(); + logicalSwitch.setDisplayName("LogicalSwitch" + timestamp); + logicalSwitch.setPortIsolationEnabled(true); + logicalSwitch.setReplicationMode("service"); + logicalSwitch.setTags(new ArrayList()); + logicalSwitch.getTags().add(new NiciraNvpTag("anto", "hugo")); + + // In the creation we don't get to specify UUID, href or schema: they don't exist yet + + logicalSwitch = api.createLogicalSwitch(logicalSwitch); + + // We can now update the new entity + logicalSwitch.setDisplayName("UpdatedLogicalSwitch" + timestamp); + api.updateLogicalSwitch(logicalSwitch, logicalSwitch.getUuid()); + + // Read them all + List logicalSwitches = api.findLogicalSwitch(); + for (final LogicalSwitch iLogicalSwitch : logicalSwitches) { + if (iLogicalSwitch.getUuid().equalsIgnoreCase(logicalSwitch.getUuid())) { + assertEquals("Read a LogicalSwitch different from the one just created and updated", logicalSwitch, iLogicalSwitch); + } + } + + // Read them filtered by uuid (get one) + logicalSwitches = api.findLogicalSwitch(logicalSwitch.getUuid()); + assertEquals("Read a LogicalSwitch different from the one just created and updated", logicalSwitch, logicalSwitches.get(0)); + assertEquals("Read a LogicalSwitch filtered by unique id (UUID) with more than one item", 1, logicalSwitches.size()); + + // Before deleting the test LogicalSwitch, test its ports + final List tags = new ArrayList(); + tags.add(new NiciraNvpTag("cs_account", "OwnerName")); + + LogicalSwitchPort logicalSwitchPort = new LogicalSwitchPort("LSwitchPort" + timestamp, tags, true); + logicalSwitchPort = api.createLogicalSwitchPort(logicalSwitch.getUuid(), logicalSwitchPort); + + logicalSwitchPort.setDisplayName("UpdatedLSwitchPort" + timestamp); + api.updateLogicalSwitchPort(logicalSwitch.getUuid(), logicalSwitchPort); + + final List logicalSwitchePorts = api.findLogicalSwitchPortsByUuid(logicalSwitch.getUuid(), logicalSwitchPort.getUuid()); + for (final LogicalSwitchPort iLSwitchPort : logicalSwitchePorts) { + if (iLSwitchPort.getUuid().equalsIgnoreCase(logicalSwitchPort.getUuid())) { + assertEquals("Read a LogicalSwitchPort different from the one just created and updated", logicalSwitchPort, iLSwitchPort); + } + } + + // And finally test attachments + final String attachmentUuid = UUID.randomUUID().toString(); + final VifAttachment vifAttachment = new VifAttachment(attachmentUuid); + api.updateLogicalSwitchPortAttachment(logicalSwitch.getUuid(), logicalSwitchPort.getUuid(), vifAttachment); + + assertEquals("Read a LogicalSwitchPort by vifAttachment different than expected", + api.findLogicalSwitchPortUuidByVifAttachmentUuid(logicalSwitch.getUuid(), vifAttachment.getVifUuid()), logicalSwitchPort.getUuid()); + + api.deleteLogicalSwitchPort(logicalSwitch.getUuid(), logicalSwitchPort.getUuid()); + + // We can now delete the new entity + api.deleteLogicalSwitch(logicalSwitch.getUuid()); + } + + @Test + public void testCRUDLogicalRouter() { + LogicalRouter logicalRouter = new LogicalRouter(); + logicalRouter.setDisplayName("LogicalRouter" + timestamp); + logicalRouter.setDistributed(true); + logicalRouter.setNatSynchronizationEnabled(true); + logicalRouter.setReplicationMode(LogicalRouter.REPLICATION_MODE_SERVICE); + final RoutingConfig routingConfig = new SingleDefaultRouteImplicitRoutingConfig( + new RouterNextHop("192.168.10.20")); + logicalRouter.setRoutingConfig(routingConfig); + + // In the creation we don't get to specify UUID, href or schema: they don't exist yet + + try { + logicalRouter = api.createLogicalRouter(logicalRouter); + + // We can now update the new entity + logicalRouter.setDisplayName("UpdatedLogicalSwitch" + timestamp); + api.updateLogicalRouter(logicalRouter, logicalRouter.getUuid()); + + // Read them all + List logicalRouters = api.findLogicalRouter(); + LogicalRouter lsInList = null; + for (final LogicalRouter iLogicalRouter : logicalRouters) { + if (iLogicalRouter.getUuid().equalsIgnoreCase(logicalRouter.getUuid())) { + lsInList = iLogicalRouter; + } + } + assertEquals("Read a LogicalRouter different from the one just created and updated", logicalRouter, lsInList); + + // Read them filtered by uuid (get one) + logicalRouters = api.findLogicalRouter(logicalRouter.getUuid()); + assertEquals("Read a LogicalRouter different from the one just created and updated", logicalRouter, logicalRouters.get(0)); + assertEquals("Read a LogicalRouter filtered by unique id (UUID) with more than one item", 1, logicalRouters.size()); + + assertEquals(logicalRouters.get(0), api.findOneLogicalRouterByUuid(logicalRouter.getUuid())); + + // Before deleting the test LogicalRouter, test its ports + final List tags = new ArrayList(); + tags.add(new NiciraNvpTag("cs_account", "OwnerName")); + + LogicalRouterPort logicalRouterPort = new LogicalRouterPort(); + logicalRouterPort.setDisplayName("LRouterPort" + timestamp); + logicalRouterPort.setTags(tags); + logicalRouterPort.setAdminStatusEnabled(true); + logicalRouterPort.setPortno(1024); + logicalRouterPort.setMacAddress("00:00:00:00:00:00"); + + final List ipAddresses = new ArrayList(); + // Add some ips to this list + logicalRouterPort.setIpAddresses(ipAddresses); + logicalRouterPort = api.createLogicalRouterPort(logicalRouter.getUuid(), logicalRouterPort); + + logicalRouterPort.setDisplayName("UpdatedLRouterPort" + timestamp); + api.updateLogicalRouterPort(logicalRouter.getUuid(), logicalRouterPort); + + final List logicalRouterePorts = api.findLogicalRouterPortsByUuid(logicalRouter.getUuid(), logicalRouterPort.getUuid()); + for (final LogicalRouterPort iLRouterPort : logicalRouterePorts) { + if (iLRouterPort.getUuid().equalsIgnoreCase(logicalRouterPort.getUuid())) { + assertEquals("Read a LogicalRouterPort different from the one just created and updated", logicalRouterPort, iLRouterPort); + } + } + + UUID.randomUUID().toString(); + + // Test CRUD for Nat Rules + SourceNatRule snr = new SourceNatRule(); + snr.setToSourceIpAddressMin("192.168.10.10"); + snr.setToSourceIpAddressMax("192.168.10.20"); + snr.setOrder(200); + final Match match = new Match(); + match.setSourceIpAddresses("192.168.150.150"); + snr.setMatch(match); + snr = (SourceNatRule) api.createLogicalRouterNatRule(logicalRouter.getUuid(), snr); + snr.setToSourceIpAddressMax("192.168.10.30"); + api.updateLogicalRouterNatRule(logicalRouter.getUuid(), snr); + + api.findNatRulesByLogicalRouterUuid(logicalRouter.getUuid()); + api.deleteLogicalRouterNatRule(logicalRouter.getUuid(), snr.getUuid()); + + api.deleteLogicalRouterPort(logicalRouter.getUuid(), logicalRouterPort.getUuid()); + + // We can now delete the new entity + api.deleteLogicalRouter(logicalRouter.getUuid()); + } catch (final NiciraNvpApiException e) { + e.printStackTrace(); + assertTrue("Errors in LogicalRouter CRUD", false); + } + } + + @Test + public void testGetControlClusterStatus() throws NiciraNvpApiException { + final ControlClusterStatus controlClusterStatus = api.getControlClusterStatus(); + final String clusterStatus = controlClusterStatus.getClusterStatus(); + final boolean correctStatus = clusterStatus.equalsIgnoreCase("stable") || + clusterStatus.equalsIgnoreCase("joining") || clusterStatus.equalsIgnoreCase("unstable"); + assertTrue("Not recognizable cluster status", correctStatus); + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NiciraNvpApiTest.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NiciraNvpApiTest.java new file mode 100644 index 0000000000..953fd48ccb --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NiciraNvpApiTest.java @@ -0,0 +1,198 @@ +// +// 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.network.nicira; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasProperty; +import static org.hamcrest.Matchers.hasSize; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.List; + +import com.cloud.utils.rest.HttpClientHelper; +import com.cloud.utils.rest.HttpUriRequestMethodMatcher; +import com.cloud.utils.rest.HttpUriRequestPathMatcher; +import com.cloud.utils.rest.HttpUriRequestQueryMatcher; + +import org.apache.commons.httpclient.HttpStatus; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.ProtocolVersion; +import org.apache.http.StatusLine; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.message.BasicStatusLine; +import org.hamcrest.Matchers; +import org.junit.Test; + +public class NiciraNvpApiTest { + private static final StatusLine HTTP_200_REPSONSE = new BasicStatusLine(new ProtocolVersion("HTTPS", 1, 1), HttpStatus.SC_OK, "OK"); + private static final StatusLine HTTP_201_REPSONSE = new BasicStatusLine(new ProtocolVersion("HTTPS", 1, 1), HttpStatus.SC_CREATED, "Created"); + + protected static final String UUID = "aaaa"; + protected static final String UUID2 = "bbbb"; + protected static final String UUID_SEC_PROFILE_URI = NiciraConstants.SEC_PROFILE_URI_PREFIX + "/aaaa"; + protected static final String SCHEMA = "myTestSchema"; + protected static final String SCHEMA2 = "myTestSchema2"; + protected static final String HREF = "myTestHref"; + protected static final String HREF2 = "myTestHref2"; + protected static final String SEC_PROFILE_JSON_RESPONSE = + "{\"uuid\" : \"aaaa\"," + + "\"display_name\" : \"myTestName\"," + + "\"href\" : \"myTestHref\"," + + "\"schema\" : \"myTestSchema\"}"; + + protected static final String SEC_PROFILE_LIST_JSON_RESPONSE = "{\"results\" : [{\"uuid\" : \"aaaa\"," + + "\"display_name\" : \"myTestName\"," + + "\"href\" : \"myTestHref\"," + + "\"schema\" : \"myTestSchema\"}," + + "{ \"uuid\" : \"bbbb\"," + + "\"display_name\" : \"myTestName2\"," + + "\"href\" : \"myTestHref2\"," + + "\"schema\" : \"myTestSchema2\"}]," + + "\"result_count\": 2}"; + + private static NiciraNvpApi buildApi(final CloseableHttpClient httpClient) { + return NiciraNvpApi.create() + .host("localhost") + .username("admin") + .password("adminpassword") + .httpClient(httpClient) + .build(); + } + + @Test + @SuppressWarnings("unchecked") + public void testFindSecurityProfile() throws Exception { + final CloseableHttpResponse response = mock(CloseableHttpResponse.class); + when(response.getStatusLine()).thenReturn(HTTP_200_REPSONSE); + when(response.getEntity()).thenReturn(new StringEntity(SEC_PROFILE_LIST_JSON_RESPONSE)); + final CloseableHttpClient httpClient = spy(HttpClientHelper.createHttpClient(2)); + doReturn(response).when(httpClient).execute(any(HttpHost.class), any(HttpRequest.class), any(HttpClientContext.class)); + final NiciraNvpApi api = buildApi(httpClient); + + final List actualProfiles = api.findSecurityProfile(); + + assertThat("Wrong number of results", actualProfiles, hasSize(2)); + assertThat("Wrong Uuid in the newly created SecurityProfile", actualProfiles, Matchers. contains( + hasProperty("uuid", equalTo(UUID)), + hasProperty("uuid", equalTo(UUID2)))); + assertThat("Wrong HREF in the newly created SecurityProfile", actualProfiles, Matchers. contains( + hasProperty("href", equalTo(HREF)), + hasProperty("href", equalTo(HREF2)))); + assertThat("Wrong Schema in the newly created SecurityProfile", actualProfiles, Matchers. contains( + hasProperty("schema", equalTo(SCHEMA)), + hasProperty("schema", equalTo(SCHEMA2)))); + verify(response, times(1)).close(); + verify(httpClient).execute(any(HttpHost.class), HttpUriRequestMethodMatcher.aMethod("GET"), any(HttpClientContext.class)); + verify(httpClient).execute(any(HttpHost.class), HttpUriRequestQueryMatcher.aQuery("fields=*"), any(HttpClientContext.class)); + verify(httpClient).execute(any(HttpHost.class), HttpUriRequestPathMatcher.aPath(NiciraConstants.SEC_PROFILE_URI_PREFIX), any(HttpClientContext.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void testFindSecurityProfileByUuid() throws Exception { + final CloseableHttpResponse response = mock(CloseableHttpResponse.class); + when(response.getStatusLine()).thenReturn(HTTP_200_REPSONSE); + when(response.getEntity()).thenReturn(new StringEntity(SEC_PROFILE_LIST_JSON_RESPONSE)); + final CloseableHttpClient httpClient = spy(HttpClientHelper.createHttpClient(2)); + doReturn(response).when(httpClient).execute(any(HttpHost.class), any(HttpRequest.class), any(HttpClientContext.class)); + final NiciraNvpApi api = buildApi(httpClient); + + final List actualProfiles = api.findSecurityProfile(UUID); + + assertThat("Wrong number of results", actualProfiles, hasSize(2)); + assertThat("Wrong Uuid in the newly created SecurityProfile", actualProfiles, Matchers. contains( + hasProperty("uuid", equalTo(UUID)), + hasProperty("uuid", equalTo(UUID2)))); + assertThat("Wrong HREF in the newly created SecurityProfile", actualProfiles, Matchers. contains( + hasProperty("href", equalTo(HREF)), + hasProperty("href", equalTo(HREF2)))); + assertThat("Wrong Schema in the newly created SecurityProfile", actualProfiles, Matchers. contains( + hasProperty("schema", equalTo(SCHEMA)), + hasProperty("schema", equalTo(SCHEMA2)))); + verify(response, times(1)).close(); + verify(httpClient).execute(any(HttpHost.class), HttpUriRequestMethodMatcher.aMethod("GET"), any(HttpClientContext.class)); + verify(httpClient).execute(any(HttpHost.class), HttpUriRequestQueryMatcher.aQueryThatContains("uuid=" + UUID), any(HttpClientContext.class)); + verify(httpClient).execute(any(HttpHost.class), HttpUriRequestQueryMatcher.aQueryThatContains("fields=*"), any(HttpClientContext.class)); + verify(httpClient).execute(any(HttpHost.class), HttpUriRequestPathMatcher.aPath(NiciraConstants.SEC_PROFILE_URI_PREFIX), any(HttpClientContext.class)); + } + + @Test + public void testCreateSecurityProfile() throws Exception { + final CloseableHttpResponse response = mock(CloseableHttpResponse.class); + when(response.getStatusLine()).thenReturn(HTTP_201_REPSONSE); + when(response.getEntity()).thenReturn(new StringEntity(SEC_PROFILE_JSON_RESPONSE)); + final CloseableHttpClient httpClient = spy(HttpClientHelper.createHttpClient(2)); + doReturn(response).when(httpClient).execute(any(HttpHost.class), any(HttpRequest.class), any(HttpClientContext.class)); + final NiciraNvpApi api = buildApi(httpClient); + + final SecurityProfile actualSecProfile = api.createSecurityProfile(new SecurityProfile()); + + assertThat("Wrong Uuid in the newly created SecurityProfile", actualSecProfile, hasProperty("uuid", equalTo(UUID))); + assertThat("Wrong Href in the newly created SecurityProfile", actualSecProfile, hasProperty("href", equalTo(HREF))); + assertThat("Wrong Schema in the newly created SecurityProfile", actualSecProfile, hasProperty("schema", equalTo(SCHEMA))); + verify(response, times(1)).close(); + verify(httpClient).execute(any(HttpHost.class), HttpUriRequestMethodMatcher.aMethod("POST"), any(HttpClientContext.class)); + verify(httpClient).execute(any(HttpHost.class), HttpUriRequestPathMatcher.aPath(NiciraConstants.SEC_PROFILE_URI_PREFIX), any(HttpClientContext.class)); + } + + @Test + public void testUpdateSecurityProfile() throws Exception { + final CloseableHttpResponse response = mock(CloseableHttpResponse.class); + when(response.getStatusLine()).thenReturn(HTTP_201_REPSONSE); + when(response.getEntity()).thenReturn(new StringEntity(SEC_PROFILE_JSON_RESPONSE)); + final CloseableHttpClient httpClient = spy(HttpClientHelper.createHttpClient(2)); + doReturn(response).when(httpClient).execute(any(HttpHost.class), any(HttpRequest.class), any(HttpClientContext.class)); + final NiciraNvpApi api = buildApi(httpClient); + + api.updateSecurityProfile(new SecurityProfile(), UUID); + + verify(response, times(1)).close(); + verify(httpClient).execute(any(HttpHost.class), HttpUriRequestMethodMatcher.aMethod("PUT"), any(HttpClientContext.class)); + verify(httpClient).execute(any(HttpHost.class), HttpUriRequestPathMatcher.aPath(NiciraConstants.SEC_PROFILE_URI_PREFIX + "/" + UUID), any(HttpClientContext.class)); + } + + @Test + public void testDeleteSecurityProfile() throws Exception { + final CloseableHttpResponse response = mock(CloseableHttpResponse.class); + when(response.getStatusLine()).thenReturn(HTTP_201_REPSONSE); + when(response.getEntity()).thenReturn(new StringEntity(SEC_PROFILE_JSON_RESPONSE)); + final CloseableHttpClient httpClient = spy(HttpClientHelper.createHttpClient(2)); + doReturn(response).when(httpClient).execute(any(HttpHost.class), any(HttpRequest.class), any(HttpClientContext.class)); + final NiciraNvpApi api = buildApi(httpClient); + + api.deleteSecurityProfile(UUID); + + verify(response, times(1)).close(); + verify(httpClient).execute(any(HttpHost.class), HttpUriRequestMethodMatcher.aMethod("DELETE"), any(HttpClientContext.class)); + verify(httpClient).execute(any(HttpHost.class), HttpUriRequestPathMatcher.aPath(NiciraConstants.SEC_PROFILE_URI_PREFIX + "/" + UUID), any(HttpClientContext.class)); + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NiciraRestClientTest.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NiciraRestClientTest.java new file mode 100644 index 0000000000..9f5201cadf --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NiciraRestClientTest.java @@ -0,0 +1,214 @@ +// +// 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.network.nicira; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.isEmptyOrNullString; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.sameInstance; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.powermock.api.mockito.PowerMockito.spy; +import static org.powermock.api.mockito.PowerMockito.verifyPrivate; + +import java.util.HashMap; +import java.util.Map; + +import com.cloud.utils.rest.CloudstackRESTException; +import com.cloud.utils.rest.HttpMethods; +import com.cloud.utils.rest.HttpRequestMatcher; +import com.cloud.utils.rest.HttpUriRequestBuilder; + +import org.apache.http.HttpHost; +import org.apache.http.ProtocolVersion; +import org.apache.http.StatusLine; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.message.BasicStatusLine; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +@RunWith(PowerMockRunner.class) +@PrepareForTest(NiciraRestClient.class) +public class NiciraRestClientTest { + + private static final int HTTPS_PORT = 443; + + private static final String HTTPS = "HTTPS"; + private static final String LOGIN_PATH = "/login"; + private static final String LOCALHOST = "localhost"; + private static final String ADMIN = "admin"; + private static final String ADMIN_PASSWORD = "adminpassword"; + + private static final HttpHost HTTP_HOST = new HttpHost(LOCALHOST, HTTPS_PORT, HTTPS); + + private static final StatusLine HTTP_200_STATUSLINE = new BasicStatusLine(new ProtocolVersion(HTTPS, 1, 1), 200, "OK"); + private static final StatusLine HTTP_401_STATUSLINE = new BasicStatusLine(new ProtocolVersion(HTTPS, 1, 1), 401, "Unauthorized"); + + private static final Map loginParameters = new HashMap(); + private static HttpUriRequest request; + private static HttpUriRequest loginRequest; + private final CloseableHttpClient httpClient = mock(CloseableHttpClient.class); + private final CloseableHttpResponse mockResponse = mock(CloseableHttpResponse.class); + private HttpClientContext httpClientContext; + private NiciraRestClient client; + + @BeforeClass + public static void setupClass() { + loginParameters.put("username", ADMIN); + loginParameters.put("password", ADMIN_PASSWORD); + request = HttpUriRequestBuilder.create() + .method(HttpMethods.GET) + .path("/path") + .build(); + loginRequest = HttpUriRequestBuilder.create() + .method(HttpMethods.POST) + .methodParameters(loginParameters) + .path(LOGIN_PATH) + .build(); + } + + @Before + public void setup() { + httpClientContext = HttpClientContext.create(); + client = spy(NiciraRestClient.create() + .client(httpClient) + .clientContext(httpClientContext) + .hostname(LOCALHOST) + .username(ADMIN) + .password(ADMIN_PASSWORD) + .loginUrl(LOGIN_PATH) + .executionLimit(5) + .build()); + } + + @Test + public void testExecuteSuccessAtFirstAttempt() throws Exception { + when(mockResponse.getStatusLine()).thenReturn(HTTP_200_STATUSLINE); + when(httpClient.execute(eq(HTTP_HOST), eq(request), eq(httpClientContext))).thenReturn(mockResponse); + + final CloseableHttpResponse response = client.execute(request); + + assertThat(response, notNullValue()); + assertThat(response, sameInstance(mockResponse)); + verifyPrivate(client).invoke("execute", request, 0); + } + + @Test + public void testExecuteUnauthorizedThenSuccess() throws Exception { + when(mockResponse.getStatusLine()) + .thenReturn(HTTP_401_STATUSLINE) + .thenReturn(HTTP_200_STATUSLINE) + .thenReturn(HTTP_200_STATUSLINE); + when(httpClient.execute(eq(HTTP_HOST), HttpRequestMatcher.eq(request), eq(httpClientContext))) + .thenReturn(mockResponse) + .thenReturn(mockResponse); + when(httpClient.execute(eq(HTTP_HOST), HttpRequestMatcher.eq(loginRequest), eq(httpClientContext))) + .thenReturn(mockResponse); + + final CloseableHttpResponse response = client.execute(request); + + assertThat(response, notNullValue()); + assertThat(response, sameInstance(mockResponse)); + verifyPrivate(client).invoke("execute", HttpRequestMatcher.eq(request), eq(0)); + verifyPrivate(client).invoke("execute", HttpRequestMatcher.eq(loginRequest), eq(401)); + verifyPrivate(client).invoke("execute", HttpRequestMatcher.eq(request), eq(200)); + } + + @Test + public void testExecuteTwoConsecutiveUnauthorizedExecutions() throws Exception { + when(mockResponse.getStatusLine()) + .thenReturn(HTTP_401_STATUSLINE) + .thenReturn(HTTP_401_STATUSLINE); + when(httpClient.execute(eq(HTTP_HOST), HttpRequestMatcher.eq(request), eq(httpClientContext))) + .thenReturn(mockResponse); + when(httpClient.execute(eq(HTTP_HOST), HttpRequestMatcher.eq(loginRequest), eq(httpClientContext))) + .thenReturn(mockResponse); + final NiciraRestClient client = spy(NiciraRestClient.create() + .client(httpClient) + .clientContext(httpClientContext) + .hostname(LOCALHOST) + .username(ADMIN) + .password(ADMIN_PASSWORD) + .loginUrl(LOGIN_PATH) + .executionLimit(2) + .build()); + + try { + client.execute(request); + fail("Expected CloudstackRESTException exception"); + } catch (final CloudstackRESTException e) { + assertThat(e.getMessage(), not(isEmptyOrNullString())); + verifyPrivate(client).invoke("execute", HttpRequestMatcher.eq(request), eq(0)); + verifyPrivate(client).invoke("execute", HttpRequestMatcher.eq(loginRequest), eq(401)); + } + } + + @Test + public void testExecuteLiveLockWhenControllerAllowsLoginAndFollowsWithUnauthorizedButDoesNotRediect() throws Exception { + when(mockResponse.getStatusLine()) + .thenReturn(HTTP_401_STATUSLINE) + .thenReturn(HTTP_200_STATUSLINE) + .thenReturn(HTTP_401_STATUSLINE) + .thenReturn(HTTP_200_STATUSLINE) + .thenReturn(HTTP_401_STATUSLINE) + .thenReturn(HTTP_200_STATUSLINE) + .thenReturn(HTTP_401_STATUSLINE) + .thenReturn(HTTP_200_STATUSLINE) + .thenReturn(HTTP_401_STATUSLINE); + when(httpClient.execute(eq(HTTP_HOST), HttpRequestMatcher.eq(request), eq(httpClientContext))) + .thenReturn(mockResponse) + .thenReturn(mockResponse) + .thenReturn(mockResponse) + .thenReturn(mockResponse) + .thenReturn(mockResponse); + when(httpClient.execute(eq(HTTP_HOST), HttpRequestMatcher.eq(loginRequest), eq(httpClientContext))) + .thenReturn(mockResponse) + .thenReturn(mockResponse) + .thenReturn(mockResponse) + .thenReturn(mockResponse); + final NiciraRestClient client = spy(NiciraRestClient.create() + .client(httpClient) + .clientContext(httpClientContext) + .hostname(LOCALHOST) + .username(ADMIN) + .password(ADMIN_PASSWORD) + .loginUrl(LOGIN_PATH) + .executionLimit(2) + .build()); + + try { + client.execute(request); + fail("Execution count should have been maxed out"); + } catch (final CloudstackRESTException e) { + assertThat(e.getMessage(), containsString("Reached max executions limit of ")); + } + } +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NiciraTagTest.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NiciraTagTest.java new file mode 100644 index 0000000000..7a31264f36 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/NiciraTagTest.java @@ -0,0 +1,58 @@ +// +// 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.network.nicira; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class NiciraTagTest { + @Test + public void testCreateTag() { + final NiciraNvpTag tag = new NiciraNvpTag("scope", "tag"); + assertEquals("scope part set", "scope", tag.getScope()); + assertEquals("tag part set", "tag", tag.getTag()); + } + + @Test + public void testCreateLongTag() { + final NiciraNvpTag tag = new NiciraNvpTag("scope", "verylongtagthatshouldattheminimumexceedthefortycharacterlenght"); + assertEquals("scope part set", "scope", tag.getScope()); + assertEquals("tag part set", "verylongtagthatshouldattheminimumexceedt", tag.getTag()); + } + + @Test + public void testSetTag() { + final NiciraNvpTag tag = new NiciraNvpTag(); + tag.setScope("scope"); + tag.setTag("tag"); + assertEquals("scope part set", "scope", tag.getScope()); + assertEquals("tag part set", "tag", tag.getTag()); + } + + @Test + public void testSetLongTag() { + final NiciraNvpTag tag = new NiciraNvpTag(); + tag.setScope("scope"); + tag.setTag("verylongtagthatshouldattheminimumexceedthefortycharacterlenght"); + assertEquals("scope part set", "scope", tag.getScope()); + assertEquals("tag part set", "verylongtagthatshouldattheminimumexceedt", tag.getTag()); + } +} \ No newline at end of file diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/RoutingConfigAdapterTest.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/RoutingConfigAdapterTest.java new file mode 100644 index 0000000000..c84102e9ce --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/nicira/RoutingConfigAdapterTest.java @@ -0,0 +1,57 @@ +// +// 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.network.nicira; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.notNullValue; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; + +import org.junit.Test; + +public class RoutingConfigAdapterTest { + private final Gson gson = new GsonBuilder() + .registerTypeAdapter(RoutingConfig.class, new RoutingConfigAdapter()) + .create(); + + @Test(expected = JsonParseException.class) + public void testRoutingConfigAdapterNoType() { + gson.fromJson("{}", RoutingConfig.class); + } + + @Test(expected = JsonParseException.class) + public void testRoutingConfigAdapterWrongType() { + gson.fromJson("{type : \"WrongType\"}", RoutingConfig.class); + } + + @Test() + public void testRoutingConfigAdapter() throws Exception { + final String jsonString = "{type : \"SingleDefaultRouteImplicitRoutingConfig\"}"; + + final SingleDefaultRouteImplicitRoutingConfig object = gson.fromJson(jsonString, SingleDefaultRouteImplicitRoutingConfig.class); + + assertThat(object, notNullValue()); + assertThat(object, instanceOf(SingleDefaultRouteImplicitRoutingConfig.class)); + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/resource/NiciraNvpRequestWrapperTest.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/resource/NiciraNvpRequestWrapperTest.java new file mode 100644 index 0000000000..fd8f3c33ea --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/resource/NiciraNvpRequestWrapperTest.java @@ -0,0 +1,250 @@ +// +// 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.network.resource; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.ConfigurePublicIpsOnLogicalRouterCommand; +import com.cloud.agent.api.CreateLogicalSwitchCommand; +import com.cloud.agent.api.DeleteLogicalRouterCommand; +import com.cloud.agent.api.DeleteLogicalSwitchCommand; +import com.cloud.agent.api.DeleteLogicalSwitchPortCommand; +import com.cloud.agent.api.MaintainCommand; +import com.cloud.agent.api.ReadyCommand; +import com.cloud.agent.api.UpdateLogicalSwitchPortCommand; +import com.cloud.network.nicira.LogicalRouterPort; +import com.cloud.network.nicira.LogicalSwitch; +import com.cloud.network.nicira.NiciraNvpApi; +import com.cloud.network.nicira.NiciraNvpApiException; +import com.cloud.network.nicira.VifAttachment; + +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; + +public class NiciraNvpRequestWrapperTest { + + @Mock + private final NiciraNvpResource niciraNvpResource = Mockito.mock(NiciraNvpResource.class); + + @Test + public void testReadyCommandWrapper() { + final ReadyCommand command = new ReadyCommand(); + + final NiciraNvpRequestWrapper wrapper = NiciraNvpRequestWrapper.getInstance(); + assertNotNull(wrapper); + + final Answer answer = wrapper.execute(command, niciraNvpResource); + + assertTrue(answer.getResult()); + } + + @Test + public void testMaintainCommandWrapper() { + final MaintainCommand command = new MaintainCommand(); + + final NiciraNvpRequestWrapper wrapper = NiciraNvpRequestWrapper.getInstance(); + assertNotNull(wrapper); + + final Answer answer = wrapper.execute(command, niciraNvpResource); + + assertTrue(answer.getResult()); + } + + @Test + public void testCreateLogicalSwitchCommandWrapper() { + final NiciraNvpApi niciraNvpApi = Mockito.mock(NiciraNvpApi.class); + final NiciraNvpUtilities niciraNvpUtilities = Mockito.mock(NiciraNvpUtilities.class); + final LogicalSwitch logicalSwitch = Mockito.mock(LogicalSwitch.class); + + final String transportUuid = "d2e05a9e-7120-4487-a5fc-414ab36d9345"; + final String transportType = "stt"; + final String name = "logicalswitch"; + final String ownerName = "owner"; + + final CreateLogicalSwitchCommand command = new CreateLogicalSwitchCommand(transportUuid, transportType, name, ownerName); + + final String truncated = "lswitch-" + command.getName(); + + when(niciraNvpResource.getNiciraNvpUtilities()).thenReturn(niciraNvpUtilities); + when(niciraNvpUtilities.createLogicalSwitch()).thenReturn(logicalSwitch); + when(niciraNvpResource.truncate("lswitch-" + command.getName(), NiciraNvpResource.NAME_MAX_LEN)).thenReturn(truncated); + when(niciraNvpResource.getNiciraNvpApi()).thenReturn(niciraNvpApi); + + try { + when(niciraNvpApi.createLogicalSwitch(logicalSwitch)).thenReturn(logicalSwitch); + when(logicalSwitch.getUuid()).thenReturn(transportUuid); + } catch (final NiciraNvpApiException e) { + fail(e.getMessage()); + } + + final NiciraNvpRequestWrapper wrapper = NiciraNvpRequestWrapper.getInstance(); + assertNotNull(wrapper); + + final Answer answer = wrapper.execute(command, niciraNvpResource); + + assertTrue(answer.getResult()); + } + + @Test + public void testDeleteLogicalSwitchCommandWrapper() { + final NiciraNvpApi niciraNvpApi = Mockito.mock(NiciraNvpApi.class); + + final String logicalSwitchUuid = "d2e05a9e-7120-4487-a5fc-414ab36d9345"; + + final DeleteLogicalSwitchCommand command = new DeleteLogicalSwitchCommand(logicalSwitchUuid); + + when(niciraNvpResource.getNiciraNvpApi()).thenReturn(niciraNvpApi); + + try { + doNothing().when(niciraNvpApi).deleteLogicalSwitch(command.getLogicalSwitchUuid()); + } catch (final NiciraNvpApiException e) { + fail(e.getMessage()); + } + + final NiciraNvpRequestWrapper wrapper = NiciraNvpRequestWrapper.getInstance(); + assertNotNull(wrapper); + + final Answer answer = wrapper.execute(command, niciraNvpResource); + + assertTrue(answer.getResult()); + } + + @Test + public void testConfigurePublicIpsOnLogicalRouterCommand() { + final NiciraNvpApi niciraNvpApi = Mockito.mock(NiciraNvpApi.class); + final LogicalRouterPort port1 = Mockito.mock(LogicalRouterPort.class); + + final List listPorts = new ArrayList(); + listPorts.add(port1); + + final String logicalRouterUuid = "d2e05a9e-7120-4487-a5fc-414ab36d9345"; + final String l3GatewayServiceUuid = "d2e05a9e-7120-4487-a5fc-414ab36d9345"; + final List publicCidrs = new ArrayList(); + publicCidrs.add("10.1.1.0/24"); + + final ConfigurePublicIpsOnLogicalRouterCommand command = new ConfigurePublicIpsOnLogicalRouterCommand(logicalRouterUuid, l3GatewayServiceUuid, publicCidrs); + + when(niciraNvpResource.getNiciraNvpApi()).thenReturn(niciraNvpApi); + + try { + when(niciraNvpApi.findLogicalRouterPortByGatewayServiceUuid(command.getLogicalRouterUuid(), command.getL3GatewayServiceUuid())).thenReturn(listPorts); + doNothing().when(niciraNvpApi).updateLogicalRouterPort(command.getLogicalRouterUuid(), port1); + } catch (final NiciraNvpApiException e) { + fail(e.getMessage()); + } + + final NiciraNvpRequestWrapper wrapper = NiciraNvpRequestWrapper.getInstance(); + assertNotNull(wrapper); + + final Answer answer = wrapper.execute(command, niciraNvpResource); + + assertTrue(answer.getResult()); + } + + @Test + public void testDeleteLogicalSwitchPortCommand() { + final NiciraNvpApi niciraNvpApi = Mockito.mock(NiciraNvpApi.class); + + final String logicalSwitchUuid = "d2e05a9e-7120-4487-a5fc-414ab36d9345"; + final String logicalSwitchPortUuid = "d2e05a9e-7120-4487-a5fc-414ab36d9345"; + + final DeleteLogicalSwitchPortCommand command = new DeleteLogicalSwitchPortCommand(logicalSwitchUuid, logicalSwitchPortUuid); + + when(niciraNvpResource.getNiciraNvpApi()).thenReturn(niciraNvpApi); + + try { + doNothing().when(niciraNvpApi).deleteLogicalSwitchPort(command.getLogicalSwitchUuid(), command.getLogicalSwitchPortUuid()); + } catch (final NiciraNvpApiException e) { + fail(e.getMessage()); + } + + final NiciraNvpRequestWrapper wrapper = NiciraNvpRequestWrapper.getInstance(); + assertNotNull(wrapper); + + final Answer answer = wrapper.execute(command, niciraNvpResource); + + assertTrue(answer.getResult()); + } + + @Test + public void testDeleteLogicalRouterCommand() { + final NiciraNvpApi niciraNvpApi = Mockito.mock(NiciraNvpApi.class); + + final String logicalRouterUuid = "d2e05a9e-7120-4487-a5fc-414ab36d9345"; + + final DeleteLogicalRouterCommand command = new DeleteLogicalRouterCommand(logicalRouterUuid); + + when(niciraNvpResource.getNiciraNvpApi()).thenReturn(niciraNvpApi); + + try { + doNothing().when(niciraNvpApi).deleteLogicalRouter(command.getLogicalRouterUuid()); + } catch (final NiciraNvpApiException e) { + fail(e.getMessage()); + } + + final NiciraNvpRequestWrapper wrapper = NiciraNvpRequestWrapper.getInstance(); + assertNotNull(wrapper); + + final Answer answer = wrapper.execute(command, niciraNvpResource); + + assertTrue(answer.getResult()); + } + + @Test + public void testUpdateLogicalSwitchPortCommand() { + final NiciraNvpApi niciraNvpApi = Mockito.mock(NiciraNvpApi.class); + final NiciraNvpUtilities niciraNvpUtilities = Mockito.mock(NiciraNvpUtilities.class); + final VifAttachment vifAttachment = Mockito.mock(VifAttachment.class); + + final String logicalSwitchPortUuid = "d2e05a9e-7120-4487-a5fc-414ab36d9345"; + final String logicalSwitchUuid = "d2e05a9e-7120-4487-a5fc-414ab36d9345"; + final String attachmentUuid = "d2e05a9e-7120-4487-a5fc-414ab36d9345"; + final String ownerName = "admin"; + final String nicName = "eth0"; + + final UpdateLogicalSwitchPortCommand command = new UpdateLogicalSwitchPortCommand(logicalSwitchPortUuid, logicalSwitchUuid, attachmentUuid, ownerName, nicName); + + when(niciraNvpResource.getNiciraNvpUtilities()).thenReturn(niciraNvpUtilities); + when(niciraNvpResource.getNiciraNvpApi()).thenReturn(niciraNvpApi); + + try { + when(niciraNvpUtilities.createVifAttachment(attachmentUuid)).thenReturn(vifAttachment); + doNothing().when(niciraNvpApi).updateLogicalSwitchPortAttachment(logicalSwitchUuid, logicalSwitchPortUuid, vifAttachment); + } catch (final NiciraNvpApiException e) { + fail(e.getMessage()); + } + + final NiciraNvpRequestWrapper wrapper = NiciraNvpRequestWrapper.getInstance(); + assertNotNull(wrapper); + + final Answer answer = wrapper.execute(command, niciraNvpResource); + + assertTrue(answer.getResult()); + } +} \ No newline at end of file diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/resource/NiciraNvpResourceTest.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/resource/NiciraNvpResourceTest.java new file mode 100644 index 0000000000..1f8ab7df8a --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/resource/NiciraNvpResourceTest.java @@ -0,0 +1,829 @@ +// +// 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.network.resource; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.argThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import javax.naming.ConfigurationException; + +import com.cloud.agent.api.ConfigurePortForwardingRulesOnLogicalRouterAnswer; +import com.cloud.agent.api.ConfigurePortForwardingRulesOnLogicalRouterCommand; +import com.cloud.agent.api.ConfigurePublicIpsOnLogicalRouterAnswer; +import com.cloud.agent.api.ConfigurePublicIpsOnLogicalRouterCommand; +import com.cloud.agent.api.ConfigureStaticNatRulesOnLogicalRouterAnswer; +import com.cloud.agent.api.ConfigureStaticNatRulesOnLogicalRouterCommand; +import com.cloud.agent.api.CreateLogicalRouterAnswer; +import com.cloud.agent.api.CreateLogicalRouterCommand; +import com.cloud.agent.api.CreateLogicalSwitchAnswer; +import com.cloud.agent.api.CreateLogicalSwitchCommand; +import com.cloud.agent.api.CreateLogicalSwitchPortAnswer; +import com.cloud.agent.api.CreateLogicalSwitchPortCommand; +import com.cloud.agent.api.DeleteLogicalRouterAnswer; +import com.cloud.agent.api.DeleteLogicalRouterCommand; +import com.cloud.agent.api.DeleteLogicalSwitchAnswer; +import com.cloud.agent.api.DeleteLogicalSwitchCommand; +import com.cloud.agent.api.DeleteLogicalSwitchPortAnswer; +import com.cloud.agent.api.DeleteLogicalSwitchPortCommand; +import com.cloud.agent.api.FindLogicalSwitchPortAnswer; +import com.cloud.agent.api.FindLogicalSwitchPortCommand; +import com.cloud.agent.api.PingCommand; +import com.cloud.agent.api.StartupCommand; +import com.cloud.agent.api.UpdateLogicalSwitchPortAnswer; +import com.cloud.agent.api.UpdateLogicalSwitchPortCommand; +import com.cloud.agent.api.to.PortForwardingRuleTO; +import com.cloud.agent.api.to.StaticNatRuleTO; +import com.cloud.host.Host; +import com.cloud.network.nicira.Attachment; +import com.cloud.network.nicira.ControlClusterStatus; +import com.cloud.network.nicira.DestinationNatRule; +import com.cloud.network.nicira.LogicalRouter; +import com.cloud.network.nicira.LogicalRouterPort; +import com.cloud.network.nicira.LogicalSwitch; +import com.cloud.network.nicira.LogicalSwitchPort; +import com.cloud.network.nicira.NatRule; +import com.cloud.network.nicira.NiciraNvpApi; +import com.cloud.network.nicira.NiciraNvpApiException; +import com.cloud.network.nicira.SourceNatRule; +import com.cloud.network.utils.CommandRetryUtility; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentMatcher; + +public class NiciraNvpResourceTest { + NiciraNvpApi nvpApi = mock(NiciraNvpApi.class); + NiciraNvpResource resource; + Map parameters; + + private CommandRetryUtility retryUtility; + + @Before + public void setUp() { + resource = new NiciraNvpResource() { + @Override + protected NiciraNvpApi createNiciraNvpApi(final String host, final String username, final String password) { + return nvpApi; + } + }; + + parameters = new HashMap(); + parameters.put("name", "nvptestdevice"); + parameters.put("ip", "127.0.0.1"); + parameters.put("adminuser", "adminuser"); + parameters.put("guid", "aaaaa-bbbbb-ccccc"); + parameters.put("zoneId", "blublub"); + parameters.put("adminpass", "adminpass"); + + retryUtility = CommandRetryUtility.getInstance(); + retryUtility.setServerResource(resource); + } + + @Test(expected = ConfigurationException.class) + public void resourceConfigureFailure() throws ConfigurationException { + resource.configure("NiciraNvpResource", Collections. emptyMap()); + } + + @Test + public void testInitialization() throws ConfigurationException { + resource.configure("NiciraNvpResource", parameters); + + final StartupCommand[] sc = resource.initialize(); + assertTrue(sc.length == 1); + assertTrue("Incorrect startup command GUID", "aaaaa-bbbbb-ccccc".equals(sc[0].getGuid())); + assertTrue("Incorrect NVP device name", "nvptestdevice".equals(sc[0].getName())); + assertTrue("Incorrect Data Center", "blublub".equals(sc[0].getDataCenter())); + } + + @Test + public void testPingCommandStatusOk() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + + final ControlClusterStatus ccs = mock(ControlClusterStatus.class); + when(ccs.getClusterStatus()).thenReturn("stable"); + when(nvpApi.getControlClusterStatus()).thenReturn(ccs); + + final PingCommand ping = resource.getCurrentStatus(42); + assertTrue(ping != null); + assertTrue(ping.getHostId() == 42); + assertTrue(ping.getHostType() == Host.Type.L2Networking); + } + + @Test + public void testPingCommandStatusFail() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + + final ControlClusterStatus ccs = mock(ControlClusterStatus.class); + when(ccs.getClusterStatus()).thenReturn("unstable"); + when(nvpApi.getControlClusterStatus()).thenReturn(ccs); + + final PingCommand ping = resource.getCurrentStatus(42); + assertTrue(ping == null); + } + + @Test + public void testPingCommandStatusApiException() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + + final ControlClusterStatus ccs = mock(ControlClusterStatus.class); + when(ccs.getClusterStatus()).thenReturn("unstable"); + when(nvpApi.getControlClusterStatus()).thenThrow(new NiciraNvpApiException()); + + final PingCommand ping = resource.getCurrentStatus(42); + assertTrue(ping == null); + } + + @Test + public void testRetries() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + + final LogicalSwitch ls = mock(LogicalSwitch.class); + when(ls.getUuid()).thenReturn("cccc").thenReturn("cccc"); + when(nvpApi.createLogicalSwitch((LogicalSwitch) any())).thenThrow(new NiciraNvpApiException()).thenThrow(new NiciraNvpApiException()).thenReturn(ls); + + final CreateLogicalSwitchCommand clsc = new CreateLogicalSwitchCommand((String) parameters.get("guid"), "stt", "loigicalswitch", "owner"); + final CreateLogicalSwitchAnswer clsa = (CreateLogicalSwitchAnswer) resource.executeRequest(clsc); + assertTrue(clsa.getResult()); + } + + @Test + public void testCreateLogicalSwitch() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + + final LogicalSwitch ls = mock(LogicalSwitch.class); + when(ls.getUuid()).thenReturn("cccc").thenReturn("cccc"); + when(nvpApi.createLogicalSwitch((LogicalSwitch) any())).thenReturn(ls); + + final CreateLogicalSwitchCommand clsc = new CreateLogicalSwitchCommand((String) parameters.get("guid"), "stt", "loigicalswitch", "owner"); + final CreateLogicalSwitchAnswer clsa = (CreateLogicalSwitchAnswer) resource.executeRequest(clsc); + assertTrue(clsa.getResult()); + assertTrue("cccc".equals(clsa.getLogicalSwitchUuid())); + } + + @Test + public void testCreateLogicalSwitchApiException() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + + final LogicalSwitch ls = mock(LogicalSwitch.class); + when(ls.getUuid()).thenReturn("cccc").thenReturn("cccc"); + when(nvpApi.createLogicalSwitch((LogicalSwitch) any())).thenThrow(new NiciraNvpApiException()); + + final CreateLogicalSwitchCommand clsc = new CreateLogicalSwitchCommand((String) parameters.get("guid"), "stt", "loigicalswitch", "owner"); + final CreateLogicalSwitchAnswer clsa = (CreateLogicalSwitchAnswer) resource.executeRequest(clsc); + assertFalse(clsa.getResult()); + } + + @Test + public void testDeleteLogicalSwitch() throws ConfigurationException { + resource.configure("NiciraNvpResource", parameters); + + final DeleteLogicalSwitchCommand dlsc = new DeleteLogicalSwitchCommand("cccc"); + final DeleteLogicalSwitchAnswer dlsa = (DeleteLogicalSwitchAnswer) resource.executeRequest(dlsc); + assertTrue(dlsa.getResult()); + } + + @Test + public void testDeleteLogicalSwitchApiException() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + + doThrow(new NiciraNvpApiException()).when(nvpApi).deleteLogicalSwitch((String) any()); + + final DeleteLogicalSwitchCommand dlsc = new DeleteLogicalSwitchCommand("cccc"); + final DeleteLogicalSwitchAnswer dlsa = (DeleteLogicalSwitchAnswer) resource.executeRequest(dlsc); + assertFalse(dlsa.getResult()); + } + + @Test + public void testCreateLogicalSwitchPort() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + + final LogicalSwitchPort lsp = mock(LogicalSwitchPort.class); + when(lsp.getUuid()).thenReturn("eeee"); + when(nvpApi.createLogicalSwitchPort(eq("cccc"), (LogicalSwitchPort) any())).thenReturn(lsp); + + final CreateLogicalSwitchPortCommand clspc = new CreateLogicalSwitchPortCommand("cccc", "dddd", "owner", "nicname"); + final CreateLogicalSwitchPortAnswer clspa = (CreateLogicalSwitchPortAnswer) resource.executeRequest(clspc); + assertTrue(clspa.getResult()); + assertTrue("eeee".equals(clspa.getLogicalSwitchPortUuid())); + + } + + @Test + public void testCreateLogicalSwitchPortApiExceptionInCreate() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + + final LogicalSwitchPort lsp = mock(LogicalSwitchPort.class); + when(lsp.getUuid()).thenReturn("eeee"); + when(nvpApi.createLogicalSwitchPort(eq("cccc"), (LogicalSwitchPort) any())).thenThrow(new NiciraNvpApiException()); + + final CreateLogicalSwitchPortCommand clspc = new CreateLogicalSwitchPortCommand("cccc", "dddd", "owner", "nicname"); + final CreateLogicalSwitchPortAnswer clspa = (CreateLogicalSwitchPortAnswer) resource.executeRequest(clspc); + assertFalse(clspa.getResult()); + } + + @Test + public void testCreateLogicalSwitchPortApiExceptionInModify() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + + final LogicalSwitchPort lsp = mock(LogicalSwitchPort.class); + when(lsp.getUuid()).thenReturn("eeee"); + when(nvpApi.createLogicalSwitchPort(eq("cccc"), (LogicalSwitchPort) any())).thenReturn(lsp); + doThrow(new NiciraNvpApiException()).when(nvpApi).updateLogicalSwitchPortAttachment((String) any(), (String) any(), (Attachment) any()); + + final CreateLogicalSwitchPortCommand clspc = new CreateLogicalSwitchPortCommand("cccc", "dddd", "owner", "nicname"); + final CreateLogicalSwitchPortAnswer clspa = (CreateLogicalSwitchPortAnswer) resource.executeRequest(clspc); + assertFalse(clspa.getResult()); + verify(nvpApi, atLeastOnce()).deleteLogicalSwitchPort((String) any(), (String) any()); + } + + @Test + public void testDeleteLogicalSwitchPortException() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + + doThrow(new NiciraNvpApiException()).when(nvpApi).deleteLogicalSwitchPort((String) any(), (String) any()); + final DeleteLogicalSwitchPortAnswer dlspa = (DeleteLogicalSwitchPortAnswer) resource.executeRequest(new DeleteLogicalSwitchPortCommand("aaaa", "bbbb")); + assertFalse(dlspa.getResult()); + } + + @Test + public void testUpdateLogicalSwitchPortException() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + + doThrow(new NiciraNvpApiException()).when(nvpApi).updateLogicalSwitchPortAttachment((String) any(), (String) any(), (Attachment) any()); + final UpdateLogicalSwitchPortAnswer dlspa = + (UpdateLogicalSwitchPortAnswer) resource.executeRequest(new UpdateLogicalSwitchPortCommand("aaaa", "bbbb", "cccc", "owner", "nicname")); + assertFalse(dlspa.getResult()); + } + + @Test + public void testFindLogicalSwitchPort() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + + final List lspl = Arrays.asList(new LogicalSwitchPort()); + when(nvpApi.findLogicalSwitchPortsByUuid("aaaa", "bbbb")).thenReturn(lspl); + + final FindLogicalSwitchPortAnswer flspa = (FindLogicalSwitchPortAnswer) resource.executeRequest(new FindLogicalSwitchPortCommand("aaaa", "bbbb")); + assertTrue(flspa.getResult()); + } + + @Test + public void testFindLogicalSwitchPortNotFound() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + + @SuppressWarnings("unchecked") + final List lspl = Collections.EMPTY_LIST; + when(nvpApi.findLogicalSwitchPortsByUuid("aaaa", "bbbb")).thenReturn(lspl); + + final FindLogicalSwitchPortAnswer flspa = (FindLogicalSwitchPortAnswer) resource.executeRequest(new FindLogicalSwitchPortCommand("aaaa", "bbbb")); + assertFalse(flspa.getResult()); + } + + @Test + public void testFindLogicalSwitchPortApiException() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + + when(nvpApi.findLogicalSwitchPortsByUuid("aaaa", "bbbb")).thenThrow(new NiciraNvpApiException()); + + final FindLogicalSwitchPortAnswer flspa = (FindLogicalSwitchPortAnswer) resource.executeRequest(new FindLogicalSwitchPortCommand("aaaa", "bbbb")); + assertFalse(flspa.getResult()); + } + + @Test + public void testCreateLogicalRouter() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + + final LogicalRouter lrc = mock(LogicalRouter.class); + final LogicalRouterPort lrp = mock(LogicalRouterPort.class); + final LogicalSwitchPort lsp = mock(LogicalSwitchPort.class); + when(lrc.getUuid()).thenReturn("ccccc"); + when(lrp.getUuid()).thenReturn("ddddd").thenReturn("eeeee"); + when(lsp.getUuid()).thenReturn("fffff"); + when(nvpApi.createLogicalRouter((LogicalRouter) any())).thenReturn(lrc); + when(nvpApi.createLogicalRouterPort(eq("ccccc"), (LogicalRouterPort) any())).thenReturn(lrp); + when(nvpApi.createLogicalSwitchPort(eq("bbbbb"), (LogicalSwitchPort) any())).thenReturn(lsp); + final CreateLogicalRouterCommand clrc = new CreateLogicalRouterCommand("aaaaa", 50, "bbbbb", "lrouter", "publiccidr", "nexthop", "internalcidr", "owner"); + final CreateLogicalRouterAnswer clra = (CreateLogicalRouterAnswer) resource.executeRequest(clrc); + + assertTrue(clra.getResult()); + assertTrue("ccccc".equals(clra.getLogicalRouterUuid())); + verify(nvpApi, atLeast(1)).createLogicalRouterNatRule((String) any(), (NatRule) any()); + } + + @Test + public void testCreateLogicalRouterApiException() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + + when(nvpApi.createLogicalRouter((LogicalRouter) any())).thenThrow(new NiciraNvpApiException()); + final CreateLogicalRouterCommand clrc = new CreateLogicalRouterCommand("aaaaa", 50, "bbbbb", "lrouter", "publiccidr", "nexthop", "internalcidr", "owner"); + final CreateLogicalRouterAnswer clra = (CreateLogicalRouterAnswer) resource.executeRequest(clrc); + + assertFalse(clra.getResult()); + } + + @Test + public void testCreateLogicalRouterApiExceptionRollbackRouter() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + + final LogicalRouter lrc = mock(LogicalRouter.class); + when(lrc.getUuid()).thenReturn("ccccc"); + when(nvpApi.createLogicalRouter((LogicalRouter) any())).thenReturn(lrc); + when(nvpApi.createLogicalRouterPort(eq("ccccc"), (LogicalRouterPort) any())).thenThrow(new NiciraNvpApiException()); + final CreateLogicalRouterCommand clrc = new CreateLogicalRouterCommand("aaaaa", 50, "bbbbb", "lrouter", "publiccidr", "nexthop", "internalcidr", "owner"); + final CreateLogicalRouterAnswer clra = (CreateLogicalRouterAnswer) resource.executeRequest(clrc); + + assertFalse(clra.getResult()); + verify(nvpApi, atLeast(1)).deleteLogicalRouter(eq("ccccc")); + } + + @Test + public void testCreateLogicalRouterApiExceptionRollbackRouterAndSwitchPort() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + + final LogicalRouter lrc = mock(LogicalRouter.class); + final LogicalRouterPort lrp = mock(LogicalRouterPort.class); + final LogicalSwitchPort lsp = mock(LogicalSwitchPort.class); + when(lrc.getUuid()).thenReturn("ccccc"); + when(lrp.getUuid()).thenReturn("ddddd").thenReturn("eeeee"); + when(lsp.getUuid()).thenReturn("fffff"); + when(nvpApi.createLogicalRouter((LogicalRouter) any())).thenReturn(lrc); + when(nvpApi.createLogicalRouterPort(eq("ccccc"), (LogicalRouterPort) any())).thenReturn(lrp); + when(nvpApi.createLogicalSwitchPort(eq("bbbbb"), (LogicalSwitchPort) any())).thenReturn(lsp); + when(nvpApi.createLogicalRouterNatRule((String) any(), (NatRule) any())).thenThrow(new NiciraNvpApiException()); + final CreateLogicalRouterCommand clrc = new CreateLogicalRouterCommand("aaaaa", 50, "bbbbb", "lrouter", "publiccidr", "nexthop", "internalcidr", "owner"); + final CreateLogicalRouterAnswer clra = (CreateLogicalRouterAnswer) resource.executeRequest(clrc); + + assertFalse(clra.getResult()); + verify(nvpApi, atLeast(1)).deleteLogicalRouter(eq("ccccc")); + verify(nvpApi, atLeast(1)).deleteLogicalSwitchPort(eq("bbbbb"), eq("fffff")); + } + + @Test + public void testDeleteLogicalRouterApiException() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + + doThrow(new NiciraNvpApiException()).when(nvpApi).deleteLogicalRouter(eq("aaaaa")); + final DeleteLogicalRouterAnswer dlspa = (DeleteLogicalRouterAnswer) resource.executeRequest(new DeleteLogicalRouterCommand("aaaaa")); + assertFalse(dlspa.getResult()); + } + + @Test + public void testConfigurePublicIpsOnLogicalRouterApiException() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + + final ConfigurePublicIpsOnLogicalRouterCommand cmd = mock(ConfigurePublicIpsOnLogicalRouterCommand.class); + @SuppressWarnings("unchecked") + final List list = Collections.EMPTY_LIST; + + when(cmd.getLogicalRouterUuid()).thenReturn("aaaaa"); + when(cmd.getL3GatewayServiceUuid()).thenReturn("bbbbb"); + when(nvpApi.findLogicalRouterPortByGatewayServiceUuid("aaaaa", "bbbbb")).thenReturn(list); + doThrow(new NiciraNvpApiException()).when(nvpApi).updateLogicalRouterPort((String) any(), (LogicalRouterPort) any()); + + final ConfigurePublicIpsOnLogicalRouterAnswer answer = (ConfigurePublicIpsOnLogicalRouterAnswer) resource.executeRequest(cmd); + assertFalse(answer.getResult()); + + } + + @Test + public void testConfigurePublicIpsOnLogicalRouterRetry() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + + final ConfigurePublicIpsOnLogicalRouterCommand cmd = mock(ConfigurePublicIpsOnLogicalRouterCommand.class); + + when(cmd.getLogicalRouterUuid()).thenReturn("aaaaa"); + when(cmd.getL3GatewayServiceUuid()).thenReturn("bbbbb"); + when(nvpApi.findLogicalRouterPortByGatewayServiceUuid("aaaaa", "bbbbb")).thenThrow(new NiciraNvpApiException("retry 1")).thenThrow(new NiciraNvpApiException("retry 2")); + + final ConfigurePublicIpsOnLogicalRouterAnswer answer = (ConfigurePublicIpsOnLogicalRouterAnswer) resource.executeRequest(cmd); + assertFalse(answer.getResult()); + + } + + @Test + public void testConfigureStaticNatRulesOnLogicalRouter() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + /* + * StaticNat Outside IP: 11.11.11.11 Inside IP: 10.10.10.10 + */ + + // Mock the command + final ConfigureStaticNatRulesOnLogicalRouterCommand cmd = mock(ConfigureStaticNatRulesOnLogicalRouterCommand.class); + final StaticNatRuleTO rule = new StaticNatRuleTO(1, "11.11.11.11", null, null, "10.10.10.10", null, null, null, false, false); + final List rules = new ArrayList(); + rules.add(rule); + when(cmd.getRules()).thenReturn(rules); + when(cmd.getLogicalRouterUuid()).thenReturn("aaaaa"); + + // Mock the api find call + @SuppressWarnings("unchecked") + final List storedRules = Collections.EMPTY_LIST; + when(nvpApi.findNatRulesByLogicalRouterUuid("aaaaa")).thenReturn(storedRules); + + // Mock the api create calls + final NatRule[] rulepair = resource.generateStaticNatRulePair("10.10.10.10", "11.11.11.11"); + rulepair[0].setUuid(UUID.randomUUID()); + rulepair[1].setUuid(UUID.randomUUID()); + when(nvpApi.createLogicalRouterNatRule(eq("aaaaa"), (NatRule) any())).thenReturn(rulepair[0]).thenReturn(rulepair[1]); + + final ConfigureStaticNatRulesOnLogicalRouterAnswer a = (ConfigureStaticNatRulesOnLogicalRouterAnswer) resource.executeRequest(cmd); + + assertTrue(a.getResult()); + verify(nvpApi, atLeast(2)).createLogicalRouterNatRule(eq("aaaaa"), argThat(new ArgumentMatcher() { + @Override + public boolean matches(final Object argument) { + final NatRule rule = (NatRule) argument; + if (rule.getType().equals("DestinationNatRule") && ((DestinationNatRule) rule).getToDestinationIpAddress().equals("10.10.10.10")) { + return true; + } + if (rule.getType().equals("SourceNatRule") && ((SourceNatRule) rule).getToSourceIpAddressMin().equals("11.11.11.11")) { + return true; + } + return false; + } + })); + } + + @Test + public void testConfigureStaticNatRulesOnLogicalRouterExistingRules() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + /* + * StaticNat Outside IP: 11.11.11.11 Inside IP: 10.10.10.10 + */ + + // Mock the command + final ConfigureStaticNatRulesOnLogicalRouterCommand cmd = mock(ConfigureStaticNatRulesOnLogicalRouterCommand.class); + final StaticNatRuleTO rule = new StaticNatRuleTO(1, "11.11.11.11", null, null, "10.10.10.10", null, null, null, false, false); + final List rules = new ArrayList(); + rules.add(rule); + when(cmd.getRules()).thenReturn(rules); + when(cmd.getLogicalRouterUuid()).thenReturn("aaaaa"); + + // Mock the api create calls + final NatRule[] rulepair = resource.generateStaticNatRulePair("10.10.10.10", "11.11.11.11"); + rulepair[0].setUuid(UUID.randomUUID()); + rulepair[1].setUuid(UUID.randomUUID()); + when(nvpApi.createLogicalRouterNatRule(eq("aaaaa"), (NatRule) any())).thenReturn(rulepair[0]).thenReturn(rulepair[1]); + + // Mock the api find call + final List storedRules = Arrays.asList(rulepair); + when(nvpApi.findNatRulesByLogicalRouterUuid("aaaaa")).thenReturn(storedRules); + + final ConfigureStaticNatRulesOnLogicalRouterAnswer a = (ConfigureStaticNatRulesOnLogicalRouterAnswer) resource.executeRequest(cmd); + + assertTrue(a.getResult()); + verify(nvpApi, never()).createLogicalRouterNatRule(eq("aaaaa"), argThat(new ArgumentMatcher() { + @Override + public boolean matches(final Object argument) { + final NatRule rule = (NatRule) argument; + if (rule.getType().equals("DestinationNatRule") && ((DestinationNatRule) rule).getToDestinationIpAddress().equals("10.10.10.10")) { + return true; + } + if (rule.getType().equals("SourceNatRule") && ((SourceNatRule) rule).getToSourceIpAddressMin().equals("11.11.11.11")) { + return true; + } + return false; + } + })); + } + + @Test + public void testConfigureStaticNatRulesOnLogicalRouterRemoveRules() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + /* + * StaticNat Outside IP: 11.11.11.11 Inside IP: 10.10.10.10 + */ + + // Mock the command + final ConfigureStaticNatRulesOnLogicalRouterCommand cmd = mock(ConfigureStaticNatRulesOnLogicalRouterCommand.class); + final StaticNatRuleTO rule = new StaticNatRuleTO(1, "11.11.11.11", null, null, "10.10.10.10", null, null, null, true, false); + final List rules = new ArrayList(); + rules.add(rule); + when(cmd.getRules()).thenReturn(rules); + when(cmd.getLogicalRouterUuid()).thenReturn("aaaaa"); + + // Mock the api create calls + final NatRule[] rulepair = resource.generateStaticNatRulePair("10.10.10.10", "11.11.11.11"); + final UUID rule0Uuid = UUID.randomUUID(); + final UUID rule1Uuid = UUID.randomUUID(); + rulepair[0].setUuid(rule0Uuid); + rulepair[1].setUuid(rule1Uuid); + when(nvpApi.createLogicalRouterNatRule(eq("aaaaa"), (NatRule) any())).thenReturn(rulepair[0]).thenReturn(rulepair[1]); + + // Mock the api find call + final List storedRules = Arrays.asList(rulepair); + when(nvpApi.findNatRulesByLogicalRouterUuid("aaaaa")).thenReturn(storedRules); + + final ConfigureStaticNatRulesOnLogicalRouterAnswer a = (ConfigureStaticNatRulesOnLogicalRouterAnswer) resource.executeRequest(cmd); + + assertTrue(a.getResult()); + verify(nvpApi, atLeast(2)).deleteLogicalRouterNatRule(eq("aaaaa"), argThat(new ArgumentMatcher() { + @Override + public boolean matches(final Object argument) { + final UUID uuid = (UUID) argument; + if (rule0Uuid.equals(uuid) || rule1Uuid.equals(uuid)) { + return true; + } + return false; + } + })); + } + + @Test + public void testConfigureStaticNatRulesOnLogicalRouterRollback() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + /* + * StaticNat Outside IP: 11.11.11.11 Inside IP: 10.10.10.10 + */ + + // Mock the command + final ConfigureStaticNatRulesOnLogicalRouterCommand cmd = mock(ConfigureStaticNatRulesOnLogicalRouterCommand.class); + final StaticNatRuleTO rule = new StaticNatRuleTO(1, "11.11.11.11", null, null, "10.10.10.10", null, null, null, false, false); + final List rules = new ArrayList(); + rules.add(rule); + when(cmd.getRules()).thenReturn(rules); + when(cmd.getLogicalRouterUuid()).thenReturn("aaaaa"); + + // Mock the api create calls + final NatRule[] rulepair = resource.generateStaticNatRulePair("10.10.10.10", "11.11.11.11"); + rulepair[0].setUuid(UUID.randomUUID()); + rulepair[1].setUuid(UUID.randomUUID()); + when(nvpApi.createLogicalRouterNatRule(eq("aaaaa"), (NatRule) any())).thenReturn(rulepair[0]).thenThrow(new NiciraNvpApiException()); + + // Mock the api find call + @SuppressWarnings("unchecked") + final List storedRules = Collections.EMPTY_LIST; + when(nvpApi.findNatRulesByLogicalRouterUuid("aaaaa")).thenReturn(storedRules); + + final ConfigureStaticNatRulesOnLogicalRouterAnswer a = (ConfigureStaticNatRulesOnLogicalRouterAnswer) resource.executeRequest(cmd); + + assertFalse(a.getResult()); + verify(nvpApi, atLeastOnce()).deleteLogicalRouterNatRule(eq("aaaaa"), eq(rulepair[0].getUuid())); + } + + @Test + public void testConfigurePortForwardingRulesOnLogicalRouter() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + /* + * StaticNat Outside IP: 11.11.11.11 Inside IP: 10.10.10.10 + */ + + // Mock the command + final ConfigurePortForwardingRulesOnLogicalRouterCommand cmd = mock(ConfigurePortForwardingRulesOnLogicalRouterCommand.class); + final PortForwardingRuleTO rule = new PortForwardingRuleTO(1, "11.11.11.11", 80, 80, "10.10.10.10", 8080, 8080, "tcp", false, false); + final List rules = new ArrayList(); + rules.add(rule); + when(cmd.getRules()).thenReturn(rules); + when(cmd.getLogicalRouterUuid()).thenReturn("aaaaa"); + + // Mock the api find call + @SuppressWarnings("unchecked") + final List storedRules = Collections.EMPTY_LIST; + when(nvpApi.findNatRulesByLogicalRouterUuid("aaaaa")).thenReturn(storedRules); + + // Mock the api create calls + final NatRule[] rulepair = resource.generatePortForwardingRulePair("10.10.10.10", new int[] { 8080, 8080 }, "11.11.11.11", new int[] { 80, 80 }, "tcp"); + rulepair[0].setUuid(UUID.randomUUID()); + rulepair[1].setUuid(UUID.randomUUID()); + when(nvpApi.createLogicalRouterNatRule(eq("aaaaa"), (NatRule) any())).thenReturn(rulepair[0]).thenReturn(rulepair[1]); + + final ConfigurePortForwardingRulesOnLogicalRouterAnswer a = (ConfigurePortForwardingRulesOnLogicalRouterAnswer) resource.executeRequest(cmd); + + assertTrue(a.getResult()); + verify(nvpApi, atLeast(2)).createLogicalRouterNatRule(eq("aaaaa"), argThat(new ArgumentMatcher() { + @Override + public boolean matches(final Object argument) { + final NatRule rule = (NatRule) argument; + if (rule.getType().equals("DestinationNatRule") && ((DestinationNatRule) rule).getToDestinationIpAddress().equals("10.10.10.10")) { + return true; + } + if (rule.getType().equals("SourceNatRule") && ((SourceNatRule) rule).getToSourceIpAddressMin().equals("11.11.11.11")) { + return true; + } + return false; + } + })); + } + + @Test + public void testConfigurePortForwardingRulesOnLogicalRouterExistingRules() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + /* + * StaticNat Outside IP: 11.11.11.11 Inside IP: 10.10.10.10 + */ + + // Mock the command + final ConfigurePortForwardingRulesOnLogicalRouterCommand cmd = mock(ConfigurePortForwardingRulesOnLogicalRouterCommand.class); + final PortForwardingRuleTO rule = new PortForwardingRuleTO(1, "11.11.11.11", 80, 80, "10.10.10.10", 8080, 8080, "tcp", false, true); + final List rules = new ArrayList(); + rules.add(rule); + when(cmd.getRules()).thenReturn(rules); + when(cmd.getLogicalRouterUuid()).thenReturn("aaaaa"); + + // Mock the api create calls + final NatRule[] rulepair = resource.generatePortForwardingRulePair("10.10.10.10", new int[] { 8080, 8080 }, "11.11.11.11", new int[] { 80, 80 }, "tcp"); + rulepair[0].setUuid(UUID.randomUUID()); + rulepair[1].setUuid(UUID.randomUUID()); + when(nvpApi.createLogicalRouterNatRule(eq("aaaaa"), (NatRule) any())).thenReturn(rulepair[0]).thenReturn(rulepair[1]); + + // Mock the api find call + final List storedRules = Arrays.asList(rulepair); + when(nvpApi.findNatRulesByLogicalRouterUuid("aaaaa")).thenReturn(storedRules); + + final ConfigurePortForwardingRulesOnLogicalRouterAnswer a = (ConfigurePortForwardingRulesOnLogicalRouterAnswer) resource.executeRequest(cmd); + + assertTrue(a.getResult()); + verify(nvpApi, never()).createLogicalRouterNatRule(eq("aaaaa"), argThat(new ArgumentMatcher() { + @Override + public boolean matches(final Object argument) { + final NatRule rule = (NatRule) argument; + if (rule.getType().equals("DestinationNatRule") && ((DestinationNatRule) rule).getToDestinationIpAddress().equals("10.10.10.10")) { + return true; + } + if (rule.getType().equals("SourceNatRule") && ((SourceNatRule) rule).getToSourceIpAddressMin().equals("11.11.11.11")) { + return true; + } + return false; + } + })); + } + + @Test + public void testConfigurePortForwardingRulesOnLogicalRouterRemoveRules() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + /* + * StaticNat Outside IP: 11.11.11.11 Inside IP: 10.10.10.10 + */ + + // Mock the command + final ConfigurePortForwardingRulesOnLogicalRouterCommand cmd = mock(ConfigurePortForwardingRulesOnLogicalRouterCommand.class); + final PortForwardingRuleTO rule = new PortForwardingRuleTO(1, "11.11.11.11", 80, 80, "10.10.10.10", 8080, 8080, "tcp", true, true); + final List rules = new ArrayList(); + rules.add(rule); + when(cmd.getRules()).thenReturn(rules); + when(cmd.getLogicalRouterUuid()).thenReturn("aaaaa"); + + // Mock the api create calls + final NatRule[] rulepair = resource.generatePortForwardingRulePair("10.10.10.10", new int[] { 8080, 8080 }, "11.11.11.11", new int[] { 80, 80 }, "tcp"); + final UUID rule0Uuid = UUID.randomUUID(); + final UUID rule1Uuid = UUID.randomUUID(); + rulepair[0].setUuid(rule0Uuid); + rulepair[1].setUuid(rule1Uuid); + when(nvpApi.createLogicalRouterNatRule(eq("aaaaa"), (NatRule) any())).thenReturn(rulepair[0]).thenReturn(rulepair[1]); + + // Mock the api find call + final List storedRules = Arrays.asList(rulepair); + when(nvpApi.findNatRulesByLogicalRouterUuid("aaaaa")).thenReturn(storedRules); + + final ConfigurePortForwardingRulesOnLogicalRouterAnswer a = (ConfigurePortForwardingRulesOnLogicalRouterAnswer) resource.executeRequest(cmd); + + assertTrue(a.getResult()); + verify(nvpApi, atLeast(2)).deleteLogicalRouterNatRule(eq("aaaaa"), argThat(new ArgumentMatcher() { + @Override + public boolean matches(final Object argument) { + final UUID uuid = (UUID) argument; + if (rule0Uuid.equals(uuid) || rule1Uuid.equals(uuid)) { + return true; + } + return false; + } + })); + } + + @Test + public void testConfigurePortForwardingRulesOnLogicalRouterRollback() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + /* + * StaticNat Outside IP: 11.11.11.11 Inside IP: 10.10.10.10 + */ + + // Mock the command + final ConfigurePortForwardingRulesOnLogicalRouterCommand cmd = mock(ConfigurePortForwardingRulesOnLogicalRouterCommand.class); + final PortForwardingRuleTO rule = new PortForwardingRuleTO(1, "11.11.11.11", 80, 80, "10.10.10.10", 8080, 8080, "tcp", false, false); + final List rules = new ArrayList(); + rules.add(rule); + when(cmd.getRules()).thenReturn(rules); + when(cmd.getLogicalRouterUuid()).thenReturn("aaaaa"); + + // Mock the api create calls + final NatRule[] rulepair = resource.generatePortForwardingRulePair("10.10.10.10", new int[] { 8080, 8080 }, "11.11.11.11", new int[] { 80, 80 }, "tcp"); + rulepair[0].setUuid(UUID.randomUUID()); + rulepair[1].setUuid(UUID.randomUUID()); + when(nvpApi.createLogicalRouterNatRule(eq("aaaaa"), (NatRule) any())).thenReturn(rulepair[0]).thenThrow(new NiciraNvpApiException()); + + // Mock the api find call + @SuppressWarnings("unchecked") + final List storedRules = Collections.EMPTY_LIST; + when(nvpApi.findNatRulesByLogicalRouterUuid("aaaaa")).thenReturn(storedRules); + + final ConfigurePortForwardingRulesOnLogicalRouterAnswer a = (ConfigurePortForwardingRulesOnLogicalRouterAnswer) resource.executeRequest(cmd); + + assertFalse(a.getResult()); + verify(nvpApi, atLeastOnce()).deleteLogicalRouterNatRule(eq("aaaaa"), eq(rulepair[0].getUuid())); + } + + @Test + public void testConfigurePortForwardingRulesOnLogicalRouterPortRange() throws ConfigurationException, NiciraNvpApiException { + resource.configure("NiciraNvpResource", parameters); + /* + * StaticNat Outside IP: 11.11.11.11 Inside IP: 10.10.10.10 + */ + + // Mock the command + final ConfigurePortForwardingRulesOnLogicalRouterCommand cmd = mock(ConfigurePortForwardingRulesOnLogicalRouterCommand.class); + final PortForwardingRuleTO rule = new PortForwardingRuleTO(1, "11.11.11.11", 80, 85, "10.10.10.10", 80, 85, "tcp", false, false); + final List rules = new ArrayList(); + rules.add(rule); + when(cmd.getRules()).thenReturn(rules); + when(cmd.getLogicalRouterUuid()).thenReturn("aaaaa"); + + // Mock the api find call + @SuppressWarnings("unchecked") + final List storedRules = Collections.EMPTY_LIST; + when(nvpApi.findNatRulesByLogicalRouterUuid("aaaaa")).thenReturn(storedRules); + + // Mock the api create calls + final NatRule[] rulepair = resource.generatePortForwardingRulePair("10.10.10.10", new int[] { 80, 85 }, "11.11.11.11", new int[] { 80, 85 }, "tcp"); + rulepair[0].setUuid(UUID.randomUUID()); + rulepair[1].setUuid(UUID.randomUUID()); + when(nvpApi.createLogicalRouterNatRule(eq("aaaaa"), (NatRule) any())).thenReturn(rulepair[0]).thenReturn(rulepair[1]); + + final ConfigurePortForwardingRulesOnLogicalRouterAnswer a = (ConfigurePortForwardingRulesOnLogicalRouterAnswer) resource.executeRequest(cmd); + + // The expected result is false, Nicira does not support port ranges in DNAT + assertFalse(a.getResult()); + + } + + @Test + public void testGenerateStaticNatRulePair() { + final NatRule[] rules = resource.generateStaticNatRulePair("10.10.10.10", "11.11.11.11"); + assertTrue("DestinationNatRule".equals(rules[0].getType())); + assertTrue("SourceNatRule".equals(rules[1].getType())); + + final DestinationNatRule dnr = (DestinationNatRule) rules[0]; + assertTrue(dnr.getToDestinationIpAddress().equals("10.10.10.10")); + assertTrue(dnr.getToDestinationPort() == null); + assertTrue(dnr.getMatch().getDestinationIpAddresses().equals("11.11.11.11")); + + final SourceNatRule snr = (SourceNatRule) rules[1]; + assertTrue(snr.getToSourceIpAddressMin().equals("11.11.11.11") && snr.getToSourceIpAddressMax().equals("11.11.11.11")); + assertTrue(snr.getToSourcePort() == null); + assertTrue(snr.getMatch().getSourceIpAddresses().equals("10.10.10.10")); + } + + @Test + public void testGeneratePortForwardingRulePair() { + final NatRule[] rules = resource.generatePortForwardingRulePair("10.10.10.10", new int[] { 8080, 8080 }, "11.11.11.11", new int[] { 80, 80 }, "tcp"); + assertTrue("DestinationNatRule".equals(rules[0].getType())); + assertTrue("SourceNatRule".equals(rules[1].getType())); + + final DestinationNatRule dnr = (DestinationNatRule) rules[0]; + assertTrue(dnr.getToDestinationIpAddress().equals("10.10.10.10")); + assertTrue(dnr.getToDestinationPort() == 8080); + assertTrue(dnr.getMatch().getDestinationIpAddresses().equals("11.11.11.11")); + assertTrue(dnr.getMatch().getDestinationPort() == 80); + assertTrue(dnr.getMatch().getEthertype().equals("IPv4") && dnr.getMatch().getProtocol() == 6); + + final SourceNatRule snr = (SourceNatRule) rules[1]; + assertTrue(snr.getToSourceIpAddressMin().equals("11.11.11.11") && snr.getToSourceIpAddressMax().equals("11.11.11.11")); + assertTrue(snr.getToSourcePort() == 80); + assertTrue(snr.getMatch().getSourceIpAddresses().equals("10.10.10.10")); + assertTrue(snr.getMatch().getSourcePort() == 8080); + assertTrue(snr.getMatch().getEthertype().equals("IPv4") && rules[1].getMatch().getProtocol() == 6); + } +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/resource/wrapper/NiciraCheckHealthCommandWrapperTest.java b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/resource/wrapper/NiciraCheckHealthCommandWrapperTest.java new file mode 100644 index 0000000000..e0488e4189 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/java/com/cloud/network/resource/wrapper/NiciraCheckHealthCommandWrapperTest.java @@ -0,0 +1,80 @@ +// +// 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.network.resource.wrapper; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.CheckHealthCommand; +import com.cloud.network.nicira.ControlClusterStatus; +import com.cloud.network.nicira.NiciraNvpApi; +import com.cloud.network.nicira.NiciraNvpApiException; +import com.cloud.network.resource.NiciraNvpResource; + +import org.junit.Before; +import org.junit.Test; + +public class NiciraCheckHealthCommandWrapperTest { + + private final NiciraNvpResource niciraResource = mock(NiciraNvpResource.class); + private final NiciraNvpApi niciraApi = mock(NiciraNvpApi.class); + + @Before + public void setup() { + when(niciraResource.getNiciraNvpApi()).thenReturn(niciraApi); + } + + @Test + public void tetsExecuteWhenClusterIsNotStable() throws Exception { + when(niciraApi.getControlClusterStatus()).thenReturn(new ControlClusterStatus()); + + final NiciraCheckHealthCommandWrapper commandWrapper = new NiciraCheckHealthCommandWrapper(); + final Answer answer = commandWrapper.execute(new CheckHealthCommand(), niciraResource); + + assertThat(answer.getResult(), equalTo(false)); + } + + @SuppressWarnings("unchecked") + @Test + public void tetsExecuteWhenApiThrowsException() throws Exception { + when(niciraApi.getControlClusterStatus()).thenThrow(NiciraNvpApiException.class); + + final NiciraCheckHealthCommandWrapper commandWrapper = new NiciraCheckHealthCommandWrapper(); + final Answer answer = commandWrapper.execute(new CheckHealthCommand(), niciraResource); + + assertThat(answer.getResult(), equalTo(false)); + } + + @Test + public void tetsExecuteWhenClusterIsStable() throws Exception { + final ControlClusterStatus statusValue = mock(ControlClusterStatus.class); + when(statusValue.getClusterStatus()).thenReturn("stable"); + when(niciraApi.getControlClusterStatus()).thenReturn(statusValue); + + final NiciraCheckHealthCommandWrapper commandWrapper = new NiciraCheckHealthCommandWrapper(); + final Answer answer = commandWrapper.execute(new CheckHealthCommand(), niciraResource); + + assertThat(answer.getResult(), equalTo(true)); + } + +} diff --git a/cosmic-core/plugins/network-elements/nicira-nvp/src/test/resources/config.properties b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/resources/config.properties new file mode 100644 index 0000000000..4006e38d64 --- /dev/null +++ b/cosmic-core/plugins/network-elements/nicira-nvp/src/test/resources/config.properties @@ -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. +# + + +nvp.host=${nvp-host} +nvp.admin.user=${nvp-admin-user} +nvp.admin.pwd=${nvp-admin-pwd} diff --git a/cosmic-core/plugins/pom.xml b/cosmic-core/plugins/pom.xml new file mode 100755 index 0000000000..574ae1d250 --- /dev/null +++ b/cosmic-core/plugins/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + cosmic-plugins + Cosmic Plugin POM + pom + + cloud.cosmic + cosmic-core + 5.1.0.0-SNAPSHOT + + + + api/rate-limit + api/discovery + acl/static-role-based + affinity-group-processors/host-anti-affinity + affinity-group-processors/explicit-dedication + deployment-planners/user-concentrated-pod + deployment-planners/user-dispersing + deployment-planners/implicit-dedication + ha-planners/skip-heurestics + host-allocators/random + dedicated-resources + network-elements/nicira-nvp + storage-allocators/random + storage/image/s3 + storage/image/default + storage/volume/default + network-elements/internal-loadbalancer + + + + + cloud.cosmic + cloud-server + ${project.version} + + + cloud.cosmic + cloud-api + ${project.version} + + + cloud.cosmic + cloud-utils + ${project.version} + + + cloud.cosmic + cloud-framework-config + ${project.version} + + + cloud.cosmic + cloud-api + ${project.version} + test-jar + test + + + diff --git a/cosmic-core/plugins/storage-allocators/random/pom.xml b/cosmic-core/plugins/storage-allocators/random/pom.xml new file mode 100644 index 0000000000..e945c32502 --- /dev/null +++ b/cosmic-core/plugins/storage-allocators/random/pom.xml @@ -0,0 +1,18 @@ + + 4.0.0 + cloud-plugin-storage-allocator-random + Cosmic Plugin - Storage Allocator Random + + cloud.cosmic + cosmic-plugins + 5.1.0.0-SNAPSHOT + ../../pom.xml + + + + cloud.cosmic + cloud-engine-storage + ${project.version} + + + \ No newline at end of file diff --git a/cosmic-core/plugins/storage-allocators/random/src/main/java/org/apache/cloudstack/storage/allocator/RandomStoragePoolAllocator.java b/cosmic-core/plugins/storage-allocators/random/src/main/java/org/apache/cloudstack/storage/allocator/RandomStoragePoolAllocator.java new file mode 100644 index 0000000000..2312ff56f5 --- /dev/null +++ b/cosmic-core/plugins/storage-allocators/random/src/main/java/org/apache/cloudstack/storage/allocator/RandomStoragePoolAllocator.java @@ -0,0 +1,79 @@ +// 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 org.apache.cloudstack.storage.allocator; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.cloud.deploy.DeploymentPlan; +import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.storage.ScopeType; +import com.cloud.storage.StoragePool; +import com.cloud.vm.DiskProfile; +import com.cloud.vm.VirtualMachineProfile; + +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RandomStoragePoolAllocator extends AbstractStoragePoolAllocator { + private static final Logger s_logger = LoggerFactory.getLogger(RandomStoragePoolAllocator.class); + + @Override + public List select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo) { + + List suitablePools = new ArrayList(); + + long dcId = plan.getDataCenterId(); + Long podId = plan.getPodId(); + Long clusterId = plan.getClusterId(); + + if (podId == null) { + return null; + } + + s_logger.debug("Looking for pools in dc: " + dcId + " pod:" + podId + " cluster:" + clusterId); + List pools = _storagePoolDao.listBy(dcId, podId, clusterId, ScopeType.CLUSTER); + if (pools.size() == 0) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("No storage pools available for allocation, returning"); + } + return suitablePools; + } + + Collections.shuffle(pools); + if (s_logger.isDebugEnabled()) { + s_logger.debug("RandomStoragePoolAllocator has " + pools.size() + " pools to check for allocation"); + } + for (StoragePoolVO pool : pools) { + if (suitablePools.size() == returnUpTo) { + break; + } + StoragePool pol = (StoragePool)this.dataStoreMgr.getPrimaryDataStore(pool.getId()); + + if (filter(avoid, pol, dskCh, plan)) { + suitablePools.add(pol); + } + } + + if (s_logger.isDebugEnabled()) { + s_logger.debug("RandomStoragePoolAllocator returning " + suitablePools.size() + " suitable storage pools"); + } + + return suitablePools; + } +} diff --git a/cosmic-core/plugins/storage/image/default/pom.xml b/cosmic-core/plugins/storage/image/default/pom.xml new file mode 100644 index 0000000000..9e8b275485 --- /dev/null +++ b/cosmic-core/plugins/storage/image/default/pom.xml @@ -0,0 +1,51 @@ + + 4.0.0 + cloud-plugin-storage-image-default + Cosmic Plugin - Storage Image default provider + + cloud.cosmic + cosmic-plugins + 5.1.0.0-SNAPSHOT + ../../../pom.xml + + + + cloud.cosmic + cloud-engine-storage + ${project.version} + + + cloud.cosmic + cloud-engine-storage-image + ${project.version} + + + cloud.cosmic + cloud-engine-storage-volume + ${project.version} + + + cloud.cosmic + cloud-engine-storage-snapshot + ${project.version} + + + + + + maven-surefire-plugin + + true + + + + integration-test + + test + + + + + + + \ No newline at end of file diff --git a/cosmic-core/plugins/storage/image/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackImageStoreDriverImpl.java b/cosmic-core/plugins/storage/image/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackImageStoreDriverImpl.java new file mode 100644 index 0000000000..05174551b7 --- /dev/null +++ b/cosmic-core/plugins/storage/image/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackImageStoreDriverImpl.java @@ -0,0 +1,141 @@ +/* + * 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 org.apache.cloudstack.storage.datastore.driver; + +import java.util.UUID; + +import javax.inject.Inject; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.storage.CreateEntityDownloadURLCommand; +import com.cloud.agent.api.storage.DeleteEntityDownloadURLCommand; +import com.cloud.agent.api.to.DataStoreTO; +import com.cloud.agent.api.to.NfsTO; +import com.cloud.configuration.Config; +import com.cloud.host.dao.HostDao; +import com.cloud.storage.Storage.ImageFormat; +import com.cloud.storage.Upload; +import com.cloud.utils.exception.CloudRuntimeException; + +import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint; +import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.storage.image.BaseImageStoreDriverImpl; +import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity; +import org.apache.cloudstack.storage.image.store.ImageStoreImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CloudStackImageStoreDriverImpl extends BaseImageStoreDriverImpl { + private static final Logger s_logger = LoggerFactory.getLogger(CloudStackImageStoreDriverImpl.class); + + @Inject + ConfigurationDao _configDao; + @Inject + HostDao _hostDao; + @Inject + EndPointSelector _epSelector; + + @Override + public DataStoreTO getStoreTO(DataStore store) { + ImageStoreImpl nfsStore = (ImageStoreImpl)store; + NfsTO nfsTO = new NfsTO(); + nfsTO.setRole(store.getRole()); + nfsTO.setUrl(nfsStore.getUri()); + return nfsTO; + } + + @Override + public String createEntityExtractUrl(DataStore store, String installPath, ImageFormat format, DataObject dataObject) { + // find an endpoint to send command + EndPoint ep = _epSelector.select(store); + // Create Symlink at ssvm + String path = installPath; + String uuid = UUID.randomUUID().toString() + "." + format.getFileExtension(); + CreateEntityDownloadURLCommand cmd = new CreateEntityDownloadURLCommand(((ImageStoreEntity)store).getMountPoint(), path, uuid, dataObject.getTO()); + Answer ans = null; + if (ep == null) { + String errMsg = "No remote endpoint to send command, check if host or ssvm is down?"; + s_logger.error(errMsg); + ans = new Answer(cmd, false, errMsg); + } else { + ans = ep.sendMessage(cmd); + } + if (ans == null || !ans.getResult()) { + String errorString = "Unable to create a link for entity at " + installPath + " on ssvm," + ans.getDetails(); + s_logger.error(errorString); + throw new CloudRuntimeException(errorString); + } + // Construct actual URL locally now that the symlink exists at SSVM + return generateCopyUrl(ep.getPublicAddr(), uuid); + } + + private String generateCopyUrl(String ipAddress, String uuid) { + + String hostname = ipAddress; + String scheme = "http"; + boolean _sslCopy = false; + String sslCfg = _configDao.getValue(Config.SecStorageEncryptCopy.toString()); + String _ssvmUrlDomain = _configDao.getValue("secstorage.ssl.cert.domain"); + if (sslCfg != null) { + _sslCopy = Boolean.parseBoolean(sslCfg); + } + if(_sslCopy && (_ssvmUrlDomain == null || _ssvmUrlDomain.isEmpty())){ + s_logger.warn("Empty secondary storage url domain, ignoring SSL"); + _sslCopy = false; + } + if (_sslCopy) { + if(_ssvmUrlDomain.startsWith("*")) { + hostname = ipAddress.replace(".", "-"); + hostname = hostname + _ssvmUrlDomain.substring(1); + } else { + hostname = _ssvmUrlDomain; + } + scheme = "https"; + } + return scheme + "://" + hostname + "/userdata/" + uuid; + } + + @Override + public void deleteEntityExtractUrl(DataStore store, String installPath, String downloadUrl, Upload.Type entityType) { + // find an endpoint to send command based on the ssvm on which the url was created. + EndPoint ep = _epSelector.select(store, downloadUrl); + + // Delete Symlink at ssvm. In case of volume also delete the volume. + DeleteEntityDownloadURLCommand cmd = new DeleteEntityDownloadURLCommand(installPath, entityType, downloadUrl, ((ImageStoreEntity) store).getMountPoint()); + + Answer ans = null; + if (ep == null) { + String errMsg = "No remote endpoint to send command, check if host or ssvm is down?"; + s_logger.error(errMsg); + ans = new Answer(cmd, false, errMsg); + } else { + ans = ep.sendMessage(cmd); + } + if (ans == null || !ans.getResult()) { + String errorString = "Unable to delete the url " + downloadUrl + " for path " + installPath + " on ssvm, " + ans.getDetails(); + s_logger.error(errorString); + throw new CloudRuntimeException(errorString); + } + + } + +} diff --git a/cosmic-core/plugins/storage/image/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackImageStoreLifeCycleImpl.java b/cosmic-core/plugins/storage/image/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackImageStoreLifeCycleImpl.java new file mode 100644 index 0000000000..fa70a0bf8e --- /dev/null +++ b/cosmic-core/plugins/storage/image/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackImageStoreLifeCycleImpl.java @@ -0,0 +1,191 @@ +// 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 org.apache.cloudstack.storage.datastore.lifecycle; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; + +import com.cloud.agent.api.StoragePoolInfo; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.resource.Discoverer; +import com.cloud.resource.ResourceManager; +import com.cloud.storage.DataStoreRole; +import com.cloud.storage.ScopeType; +import com.cloud.utils.StringUtils; +import com.cloud.utils.UriUtils; + +import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.HostScope; +import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope; +import org.apache.cloudstack.storage.datastore.db.ImageStoreDao; +import org.apache.cloudstack.storage.datastore.db.ImageStoreVO; +import org.apache.cloudstack.storage.image.datastore.ImageStoreHelper; +import org.apache.cloudstack.storage.image.datastore.ImageStoreProviderManager; +import org.apache.cloudstack.storage.image.store.lifecycle.ImageStoreLifeCycle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CloudStackImageStoreLifeCycleImpl implements ImageStoreLifeCycle { + + private static final Logger s_logger = LoggerFactory.getLogger(CloudStackImageStoreLifeCycleImpl.class); + @Inject + protected ResourceManager _resourceMgr; + @Inject + protected ImageStoreDao imageStoreDao; + @Inject + ImageStoreHelper imageStoreHelper; + @Inject + ImageStoreProviderManager imageStoreMgr; + + protected List _discoverers; + + public List getDiscoverers() { + return _discoverers; + } + + public void setDiscoverers(final List discoverers) { + this._discoverers = discoverers; + } + + public CloudStackImageStoreLifeCycleImpl() { + } + + @Override + public DataStore initialize(final Map dsInfos) { + + final Long dcId = (Long) dsInfos.get("zoneId"); + final String url = (String) dsInfos.get("url"); + String name = (String) dsInfos.get("name"); + if (name == null) { + name = url; + } + final String providerName = (String) dsInfos.get("providerName"); + final DataStoreRole role = (DataStoreRole) dsInfos.get("role"); + final Map details = (Map) dsInfos.get("details"); + + String logString = ""; + if (url.contains("cifs")) { + logString = cleanPassword(url); + } else { + logString = StringUtils.cleanString(url); + } + s_logger.info("Trying to add a new data store at " + logString + " to data center " + dcId); + + URI uri = null; + try { + uri = new URI(UriUtils.encodeURIComponent(url)); + if (uri.getScheme() == null) { + throw new InvalidParameterValueException("uri.scheme is null " + StringUtils.cleanString(url) + ", add nfs:// (or cifs://) as a prefix"); + } else if (uri.getScheme().equalsIgnoreCase("nfs")) { + if (uri.getHost() == null || uri.getHost().equalsIgnoreCase("") || uri.getPath() == null || uri.getPath().equalsIgnoreCase("")) { + throw new InvalidParameterValueException("Your host and/or path is wrong. Make sure it's of the format nfs://hostname/path"); + } + } else if (uri.getScheme().equalsIgnoreCase("cifs")) { + // Don't validate against a URI encoded URI. + final URI cifsUri = new URI(url); + final String warnMsg = UriUtils.getCifsUriParametersProblems(cifsUri); + if (warnMsg != null) { + throw new InvalidParameterValueException(warnMsg); + } + } + } catch (final URISyntaxException e) { + throw new InvalidParameterValueException(url + " is not a valid uri"); + } + + if (dcId == null) { + throw new InvalidParameterValueException("DataCenter id is null, and cloudstack default image store has to be associated with a data center"); + } + + final Map imageStoreParameters = new HashMap<>(); + imageStoreParameters.put("name", name); + imageStoreParameters.put("zoneId", dcId); + imageStoreParameters.put("url", url); + imageStoreParameters.put("protocol", uri.getScheme().toLowerCase()); + imageStoreParameters.put("scope", ScopeType.ZONE); // default cloudstack provider only supports zone-wide image store + imageStoreParameters.put("providerName", providerName); + imageStoreParameters.put("role", role); + + final ImageStoreVO ids = imageStoreHelper.createImageStore(imageStoreParameters, details); + return imageStoreMgr.getImageStore(ids.getId()); + } + + @Override + public boolean attachCluster(final DataStore store, final ClusterScope scope) { + return false; + } + + @Override + public boolean attachHost(final DataStore store, final HostScope scope, final StoragePoolInfo existingInfo) { + return false; + } + + @Override + public boolean attachZone(final DataStore dataStore, final ZoneScope scope, final HypervisorType hypervisorType) { + return false; + } + + @Override + public boolean maintain(final DataStore store) { + return false; + } + + @Override + public boolean cancelMaintain(final DataStore store) { + return false; + } + + @Override + public boolean deleteDataStore(final DataStore store) { + return false; + } + + /* (non-Javadoc) + * @see org.apache.cloudstack.engine.subsystem.api.storage.DataStoreLifeCycle#migrateToObjectStore(org.apache.cloudstack.engine.subsystem.api.storage.DataStore) + */ + @Override + public boolean migrateToObjectStore(final DataStore store) { + return imageStoreHelper.convertToStagingStore(store); + } + + public static String cleanPassword(final String logString) { + String cleanLogString = null; + if (logString != null) { + cleanLogString = logString; + final String[] temp = logString.split(","); + int i = 0; + if (temp != null) { + while (i < temp.length) { + temp[i] = StringUtils.cleanString(temp[i]); + i++; + } + final List stringList = new ArrayList<>(); + Collections.addAll(stringList, temp); + cleanLogString = StringUtils.join(stringList, ","); + } + } + return cleanLogString; + } +} diff --git a/cosmic-core/plugins/storage/image/default/src/main/java/org/apache/cloudstack/storage/datastore/provider/CloudStackImageStoreProviderImpl.java b/cosmic-core/plugins/storage/image/default/src/main/java/org/apache/cloudstack/storage/datastore/provider/CloudStackImageStoreProviderImpl.java new file mode 100644 index 0000000000..18067dddcb --- /dev/null +++ b/cosmic-core/plugins/storage/image/default/src/main/java/org/apache/cloudstack/storage/datastore/provider/CloudStackImageStoreProviderImpl.java @@ -0,0 +1,104 @@ +/* + * 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 org.apache.cloudstack.storage.datastore.provider; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.inject.Inject; + +import com.cloud.storage.ScopeType; +import com.cloud.utils.component.ComponentContext; + +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreLifeCycle; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProvider; +import org.apache.cloudstack.engine.subsystem.api.storage.HypervisorHostListener; +import org.apache.cloudstack.engine.subsystem.api.storage.ImageStoreProvider; +import org.apache.cloudstack.storage.datastore.driver.CloudStackImageStoreDriverImpl; +import org.apache.cloudstack.storage.datastore.lifecycle.CloudStackImageStoreLifeCycleImpl; +import org.apache.cloudstack.storage.image.ImageStoreDriver; +import org.apache.cloudstack.storage.image.datastore.ImageStoreHelper; +import org.apache.cloudstack.storage.image.datastore.ImageStoreProviderManager; +import org.apache.cloudstack.storage.image.store.lifecycle.ImageStoreLifeCycle; +import org.springframework.stereotype.Component; + +@Component +public class CloudStackImageStoreProviderImpl implements ImageStoreProvider { + + private final String providerName = DataStoreProvider.NFS_IMAGE; + protected ImageStoreLifeCycle lifeCycle; + protected ImageStoreDriver driver; + @Inject + ImageStoreProviderManager storeMgr; + @Inject + ImageStoreHelper helper; + + @Override + public DataStoreLifeCycle getDataStoreLifeCycle() { + return lifeCycle; + } + + @Override + public String getName() { + return this.providerName; + } + + @Override + public boolean configure(Map params) { + lifeCycle = ComponentContext.inject(CloudStackImageStoreLifeCycleImpl.class); + driver = ComponentContext.inject(CloudStackImageStoreDriverImpl.class); + + storeMgr.registerDriver(this.getName(), driver); + + return true; + } + + @Override + public DataStoreDriver getDataStoreDriver() { + return this.driver; + } + + @Override + public HypervisorHostListener getHostListener() { + return null; + } + + @Override + public Set getTypes() { + Set types = new HashSet(); + types.add(DataStoreProviderType.IMAGE); + types.add(DataStoreProviderType.ImageCache); + return types; + } + + @Override + public boolean isScopeSupported(ScopeType scope) { + if (scope == ScopeType.ZONE) + return true; + return false; + } + + @Override + public boolean needDownloadSysTemplate() { + return false; + } + +} diff --git a/cosmic-core/plugins/storage/image/default/src/main/resources/META-INF/cloudstack/storage-image-default/module.properties b/cosmic-core/plugins/storage/image/default/src/main/resources/META-INF/cloudstack/storage-image-default/module.properties new file mode 100644 index 0000000000..8381f6eff1 --- /dev/null +++ b/cosmic-core/plugins/storage/image/default/src/main/resources/META-INF/cloudstack/storage-image-default/module.properties @@ -0,0 +1,18 @@ +# 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. +name=storage-image-default +parent=storage \ No newline at end of file diff --git a/cosmic-core/plugins/storage/image/default/src/main/resources/META-INF/cloudstack/storage-image-default/spring-storage-image-default-context.xml b/cosmic-core/plugins/storage/image/default/src/main/resources/META-INF/cloudstack/storage-image-default/spring-storage-image-default-context.xml new file mode 100644 index 0000000000..6d3c63c277 --- /dev/null +++ b/cosmic-core/plugins/storage/image/default/src/main/resources/META-INF/cloudstack/storage-image-default/spring-storage-image-default-context.xml @@ -0,0 +1,33 @@ + + + + + + diff --git a/cosmic-core/plugins/storage/image/s3/pom.xml b/cosmic-core/plugins/storage/image/s3/pom.xml new file mode 100644 index 0000000000..14479decd8 --- /dev/null +++ b/cosmic-core/plugins/storage/image/s3/pom.xml @@ -0,0 +1,33 @@ + + 4.0.0 + cloud-plugin-storage-image-s3 + Cosmic Plugin - Storage Image S3 provider + + cloud.cosmic + cosmic-plugins + 5.1.0.0-SNAPSHOT + ../../../pom.xml + + + + cloud.cosmic + cloud-engine-storage + ${project.version} + + + cloud.cosmic + cloud-engine-storage-image + ${project.version} + + + cloud.cosmic + cloud-engine-storage-volume + ${project.version} + + + cloud.cosmic + cloud-engine-storage-snapshot + ${project.version} + + + \ No newline at end of file diff --git a/cosmic-core/plugins/storage/image/s3/src/main/java/org/apache/cloudstack/storage/datastore/driver/S3ImageStoreDriverImpl.java b/cosmic-core/plugins/storage/image/s3/src/main/java/org/apache/cloudstack/storage/datastore/driver/S3ImageStoreDriverImpl.java new file mode 100644 index 0000000000..0ac32384c0 --- /dev/null +++ b/cosmic-core/plugins/storage/image/s3/src/main/java/org/apache/cloudstack/storage/datastore/driver/S3ImageStoreDriverImpl.java @@ -0,0 +1,110 @@ +/* + * 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 org.apache.cloudstack.storage.datastore.driver; + +import java.net.URL; +import java.util.Date; +import java.util.Map; + +import javax.inject.Inject; + +import com.cloud.agent.api.to.DataStoreTO; +import com.cloud.agent.api.to.S3TO; +import com.cloud.configuration.Config; +import com.cloud.storage.Storage.ImageFormat; +import com.cloud.utils.NumbersUtil; +import com.cloud.utils.storage.S3.S3Utils; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.storage.datastore.db.ImageStoreDetailsDao; +import org.apache.cloudstack.storage.image.BaseImageStoreDriverImpl; +import org.apache.cloudstack.storage.image.store.ImageStoreImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class S3ImageStoreDriverImpl extends BaseImageStoreDriverImpl { + private static final Logger s_logger = LoggerFactory.getLogger(S3ImageStoreDriverImpl.class); + + @Inject + ImageStoreDetailsDao _imageStoreDetailsDao; + + @Inject + ConfigurationDao _configDao; + + @Override + public DataStoreTO getStoreTO(DataStore store) { + ImageStoreImpl imgStore = (ImageStoreImpl)store; + Map details = _imageStoreDetailsDao.getDetails(imgStore.getId()); + return new S3TO(imgStore.getId(), + imgStore.getUuid(), + details.get(ApiConstants.S3_ACCESS_KEY), + details.get(ApiConstants.S3_SECRET_KEY), + details.get(ApiConstants.S3_END_POINT), + details.get(ApiConstants.S3_BUCKET_NAME), + details.get(ApiConstants.S3_SIGNER), + details.get(ApiConstants.S3_HTTPS_FLAG) == null ? false : Boolean.parseBoolean(details.get(ApiConstants.S3_HTTPS_FLAG)), + details.get(ApiConstants.S3_CONNECTION_TIMEOUT) == null ? null : Integer.valueOf(details.get(ApiConstants.S3_CONNECTION_TIMEOUT)), + details.get(ApiConstants.S3_MAX_ERROR_RETRY) == null ? null : Integer.valueOf(details.get(ApiConstants.S3_MAX_ERROR_RETRY)), + details.get(ApiConstants.S3_SOCKET_TIMEOUT) == null ? null : Integer.valueOf(details.get(ApiConstants.S3_SOCKET_TIMEOUT)), + imgStore.getCreated(), + _configDao.getValue(Config.S3EnableRRS.toString()) == null ? false : Boolean.parseBoolean(_configDao.getValue(Config.S3EnableRRS.toString())), + getMaxSingleUploadSizeInBytes(), + details.get(ApiConstants.S3_CONNECTION_TTL) == null ? null : Integer.valueOf(details.get(ApiConstants.S3_CONNECTION_TTL)), + details.get(ApiConstants.S3_USE_TCP_KEEPALIVE) == null ? null : Boolean.parseBoolean(details.get(ApiConstants.S3_USE_TCP_KEEPALIVE))); + } + + private long getMaxSingleUploadSizeInBytes() { + try { + return Long.parseLong(_configDao.getValue(Config.S3MaxSingleUploadSize.toString())) * 1024L * 1024L * 1024L; + } catch (NumberFormatException e) { + // use default 1TB + return 1024L * 1024L * 1024L * 1024L; + } + } + + @Override + public String createEntityExtractUrl(DataStore store, String key, ImageFormat format, DataObject dataObject) { + /** + * Generate a pre-signed URL for the given object. + */ + S3TO s3 = (S3TO)getStoreTO(store); + + if(s_logger.isDebugEnabled()) { + s_logger.debug("Generating pre-signed s3 entity extraction URL for object: " + key); + } + Date expiration = new Date(); + long milliSeconds = expiration.getTime(); + + // Get extract url expiration interval set in global configuration (in seconds) + String urlExpirationInterval = _configDao.getValue(Config.ExtractURLExpirationInterval.toString()); + + // Expired after configured interval (in milliseconds), default 14400 seconds + milliSeconds += 1000 * NumbersUtil.parseInt(urlExpirationInterval, 14400); + expiration.setTime(milliSeconds); + + URL s3url = S3Utils.generatePresignedUrl(s3, s3.getBucketName(), key, expiration); + + s_logger.info("Pre-Signed URL = " + s3url.toString()); + + return s3url.toString(); + } +} diff --git a/cosmic-core/plugins/storage/image/s3/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/S3ImageStoreLifeCycleImpl.java b/cosmic-core/plugins/storage/image/s3/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/S3ImageStoreLifeCycleImpl.java new file mode 100644 index 0000000000..8ecdc92a96 --- /dev/null +++ b/cosmic-core/plugins/storage/image/s3/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/S3ImageStoreLifeCycleImpl.java @@ -0,0 +1,142 @@ +// 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 org.apache.cloudstack.storage.datastore.lifecycle; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; + +import com.cloud.agent.api.StoragePoolInfo; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.resource.Discoverer; +import com.cloud.resource.ResourceManager; +import com.cloud.storage.DataStoreRole; +import com.cloud.storage.ScopeType; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.HostScope; +import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope; +import org.apache.cloudstack.storage.datastore.db.ImageStoreDao; +import org.apache.cloudstack.storage.datastore.db.ImageStoreVO; +import org.apache.cloudstack.storage.image.datastore.ImageStoreHelper; +import org.apache.cloudstack.storage.image.datastore.ImageStoreProviderManager; +import org.apache.cloudstack.storage.image.store.lifecycle.ImageStoreLifeCycle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class S3ImageStoreLifeCycleImpl implements ImageStoreLifeCycle { + + private static final Logger s_logger = LoggerFactory.getLogger(S3ImageStoreLifeCycleImpl.class); + @Inject + protected ResourceManager _resourceMgr; + @Inject + protected ImageStoreDao imageStoreDao; + @Inject + ImageStoreHelper imageStoreHelper; + @Inject + ImageStoreProviderManager imageStoreMgr; + + protected List _discoverers; + + public List getDiscoverers() { + return _discoverers; + } + + public void setDiscoverers(List discoverers) { + this._discoverers = discoverers; + } + + public S3ImageStoreLifeCycleImpl() { + } + + @SuppressWarnings("unchecked") + @Override + public DataStore initialize(Map dsInfos) { + + String url = (String)dsInfos.get("url"); + String name = (String)dsInfos.get("name"); + String providerName = (String)dsInfos.get("providerName"); + ScopeType scope = (ScopeType)dsInfos.get("scope"); + DataStoreRole role = (DataStoreRole)dsInfos.get("role"); + Map details = (Map)dsInfos.get("details"); + + s_logger.info("Trying to add a S3 store with endpoint: " + details.get(ApiConstants.S3_END_POINT)); + + Map imageStoreParameters = new HashMap(); + imageStoreParameters.put("name", name); + imageStoreParameters.put("url", url); + String protocol = "http"; + String useHttps = details.get(ApiConstants.S3_HTTPS_FLAG); + if (useHttps != null && Boolean.parseBoolean(useHttps)) { + protocol = "https"; + } + imageStoreParameters.put("protocol", protocol); + if (scope != null) { + imageStoreParameters.put("scope", scope); + } else { + imageStoreParameters.put("scope", ScopeType.REGION); + } + imageStoreParameters.put("providerName", providerName); + imageStoreParameters.put("role", role); + + ImageStoreVO ids = imageStoreHelper.createImageStore(imageStoreParameters, details); + return imageStoreMgr.getImageStore(ids.getId()); + } + + @Override + public boolean attachCluster(DataStore store, ClusterScope scope) { + return false; + } + + @Override + public boolean attachHost(DataStore store, HostScope scope, StoragePoolInfo existingInfo) { + return false; + } + + @Override + public boolean attachZone(DataStore dataStore, ZoneScope scope, HypervisorType hypervisorType) { + return false; + } + + @Override + public boolean maintain(DataStore store) { + return false; + } + + @Override + public boolean cancelMaintain(DataStore store) { + return false; + } + + @Override + public boolean deleteDataStore(DataStore store) { + return false; + } + + /* (non-Javadoc) + * @see org.apache.cloudstack.engine.subsystem.api.storage.DataStoreLifeCycle#migrateToObjectStore(org.apache.cloudstack.engine.subsystem.api.storage.DataStore) + */ + @Override + public boolean migrateToObjectStore(DataStore store) { + return false; + } + +} diff --git a/cosmic-core/plugins/storage/image/s3/src/main/java/org/apache/cloudstack/storage/datastore/provider/S3ImageStoreProviderImpl.java b/cosmic-core/plugins/storage/image/s3/src/main/java/org/apache/cloudstack/storage/datastore/provider/S3ImageStoreProviderImpl.java new file mode 100644 index 0000000000..2b92483eba --- /dev/null +++ b/cosmic-core/plugins/storage/image/s3/src/main/java/org/apache/cloudstack/storage/datastore/provider/S3ImageStoreProviderImpl.java @@ -0,0 +1,102 @@ +/* + * 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 org.apache.cloudstack.storage.datastore.provider; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.inject.Inject; + +import com.cloud.storage.ScopeType; +import com.cloud.utils.component.ComponentContext; + +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreLifeCycle; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProvider; +import org.apache.cloudstack.engine.subsystem.api.storage.HypervisorHostListener; +import org.apache.cloudstack.engine.subsystem.api.storage.ImageStoreProvider; +import org.apache.cloudstack.storage.datastore.driver.S3ImageStoreDriverImpl; +import org.apache.cloudstack.storage.datastore.lifecycle.S3ImageStoreLifeCycleImpl; +import org.apache.cloudstack.storage.image.ImageStoreDriver; +import org.apache.cloudstack.storage.image.datastore.ImageStoreHelper; +import org.apache.cloudstack.storage.image.datastore.ImageStoreProviderManager; +import org.apache.cloudstack.storage.image.store.lifecycle.ImageStoreLifeCycle; +import org.springframework.stereotype.Component; + +@Component +public class S3ImageStoreProviderImpl implements ImageStoreProvider { + + @Inject + ImageStoreProviderManager storeMgr; + @Inject + ImageStoreHelper helper; + + private final String providerName = DataStoreProvider.S3_IMAGE; + protected ImageStoreLifeCycle lifeCycle; + protected ImageStoreDriver driver; + + @Override + public DataStoreLifeCycle getDataStoreLifeCycle() { + return lifeCycle; + } + + @Override + public String getName() { + return this.providerName; + } + + @Override + public boolean configure(Map params) { + lifeCycle = ComponentContext.inject(S3ImageStoreLifeCycleImpl.class); + driver = ComponentContext.inject(S3ImageStoreDriverImpl.class); + storeMgr.registerDriver(this.getName(), driver); + return true; + } + + @Override + public DataStoreDriver getDataStoreDriver() { + return this.driver; + } + + @Override + public HypervisorHostListener getHostListener() { + return null; + } + + @Override + public Set getTypes() { + Set types = new HashSet(); + types.add(DataStoreProviderType.IMAGE); + return types; + } + + @Override + public boolean isScopeSupported(ScopeType scope) { + if (scope == ScopeType.REGION) + return true; + return false; + } + + @Override + public boolean needDownloadSysTemplate() { + return true; + } + +} diff --git a/cosmic-core/plugins/storage/image/s3/src/main/resources/META-INF/cloudstack/storage-image-s3/module.properties b/cosmic-core/plugins/storage/image/s3/src/main/resources/META-INF/cloudstack/storage-image-s3/module.properties new file mode 100644 index 0000000000..da571e2dda --- /dev/null +++ b/cosmic-core/plugins/storage/image/s3/src/main/resources/META-INF/cloudstack/storage-image-s3/module.properties @@ -0,0 +1,18 @@ +# 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. +name=storage-image-s3 +parent=storage \ No newline at end of file diff --git a/cosmic-core/plugins/storage/image/s3/src/main/resources/META-INF/cloudstack/storage-image-s3/spring-storage-image-s3-context.xml b/cosmic-core/plugins/storage/image/s3/src/main/resources/META-INF/cloudstack/storage-image-s3/spring-storage-image-s3-context.xml new file mode 100644 index 0000000000..610506340a --- /dev/null +++ b/cosmic-core/plugins/storage/image/s3/src/main/resources/META-INF/cloudstack/storage-image-s3/spring-storage-image-s3-context.xml @@ -0,0 +1,34 @@ + + + + + + + diff --git a/cosmic-core/plugins/storage/volume/default/pom.xml b/cosmic-core/plugins/storage/volume/default/pom.xml new file mode 100644 index 0000000000..a3da334e91 --- /dev/null +++ b/cosmic-core/plugins/storage/volume/default/pom.xml @@ -0,0 +1,36 @@ + + 4.0.0 + cloud-plugin-storage-volume-default + Cosmic Plugin - Storage Volume default provider + + cloud.cosmic + cosmic-plugins + 5.1.0.0-SNAPSHOT + ../../../pom.xml + + + + cloud.cosmic + cloud-engine-storage-volume + ${project.version} + + + + + + maven-surefire-plugin + + true + + + + integration-test + + test + + + + + + + \ No newline at end of file diff --git a/cosmic-core/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java b/cosmic-core/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java new file mode 100644 index 0000000000..a9f172d36e --- /dev/null +++ b/cosmic-core/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java @@ -0,0 +1,383 @@ +/* + * 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 org.apache.cloudstack.storage.datastore.driver; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import javax.inject.Inject; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.storage.ResizeVolumeAnswer; +import com.cloud.agent.api.storage.ResizeVolumeCommand; +import com.cloud.agent.api.to.DataObjectType; +import com.cloud.agent.api.to.DataStoreTO; +import com.cloud.agent.api.to.DataTO; +import com.cloud.agent.api.to.StorageFilerTO; +import com.cloud.configuration.Config; +import com.cloud.exception.StorageUnavailableException; +import com.cloud.host.Host; +import com.cloud.host.dao.HostDao; +import com.cloud.storage.CreateSnapshotPayload; +import com.cloud.storage.DataStoreRole; +import com.cloud.storage.ResizeVolumePayload; +import com.cloud.storage.Storage; +import com.cloud.storage.StorageManager; +import com.cloud.storage.StoragePool; +import com.cloud.storage.Volume; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.SnapshotDao; +import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.storage.snapshot.SnapshotManager; +import com.cloud.template.TemplateManager; +import com.cloud.utils.NumbersUtil; +import com.cloud.vm.dao.VMInstanceDao; + +import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; +import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult; +import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities; +import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint; +import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.StorageAction; +import org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.framework.async.AsyncCompletionCallback; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.storage.command.CommandResult; +import org.apache.cloudstack.storage.command.CopyCmdAnswer; +import org.apache.cloudstack.storage.command.CopyCommand; +import org.apache.cloudstack.storage.command.CreateObjectCommand; +import org.apache.cloudstack.storage.command.DeleteCommand; +import org.apache.cloudstack.storage.command.RevertSnapshotCommand; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.storage.to.SnapshotObjectTO; +import org.apache.cloudstack.storage.to.TemplateObjectTO; +import org.apache.cloudstack.storage.volume.VolumeObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CloudStackPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver { + @Override + public Map getCapabilities() { + Map caps = new HashMap(); + caps.put(DataStoreCapabilities.VOLUME_SNAPSHOT_QUIESCEVM.toString(), "false"); + return caps; + } + + private static final Logger s_logger = LoggerFactory.getLogger(CloudStackPrimaryDataStoreDriverImpl.class); + @Inject + DiskOfferingDao diskOfferingDao; + @Inject + VMTemplateDao templateDao; + @Inject + VolumeDao volumeDao; + @Inject + HostDao hostDao; + @Inject + StorageManager storageMgr; + @Inject + VMInstanceDao vmDao; + @Inject + SnapshotDao snapshotDao; + @Inject + PrimaryDataStoreDao primaryStoreDao; + @Inject + SnapshotManager snapshotMgr; + @Inject + EndPointSelector epSelector; + @Inject + ConfigurationDao configDao; + @Inject + TemplateManager templateManager; + @Inject + TemplateDataFactory templateDataFactory; + + @Override + public DataTO getTO(DataObject data) { + return null; + } + + @Override + public DataStoreTO getStoreTO(DataStore store) { + return null; + } + + public Answer createVolume(VolumeInfo volume) throws StorageUnavailableException { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Creating volume: " + volume); + } + + CreateObjectCommand cmd = new CreateObjectCommand(volume.getTO()); + EndPoint ep = epSelector.select(volume); + Answer answer = null; + if (ep == null) { + String errMsg = "No remote endpoint to send DeleteCommand, check if host or ssvm is down?"; + s_logger.error(errMsg); + answer = new Answer(cmd, false, errMsg); + } else { + answer = ep.sendMessage(cmd); + } + return answer; + } + + @Override + public ChapInfo getChapInfo(VolumeInfo volumeInfo) { + return null; + } + + @Override + public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore) { + return false; + } + + @Override + public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore) { + } + + @Override + public long getUsedBytes(StoragePool storagePool) { + return 0; + } + + @Override + public long getUsedIops(StoragePool storagePool) { + return 0; + } + + @Override + public long getVolumeSizeIncludingHypervisorSnapshotReserve(Volume volume, StoragePool pool) { + return volume.getSize(); + } + + @Override + public void createAsync(DataStore dataStore, DataObject data, AsyncCompletionCallback callback) { + String errMsg = null; + Answer answer = null; + CreateCmdResult result = new CreateCmdResult(null, null); + if (data.getType() == DataObjectType.VOLUME) { + try { + answer = createVolume((VolumeInfo) data); + if ((answer == null) || (!answer.getResult())) { + result.setSuccess(false); + if (answer != null) { + result.setResult(answer.getDetails()); + } + } else { + result.setAnswer(answer); + } + } catch (StorageUnavailableException e) { + s_logger.debug("failed to create volume", e); + errMsg = e.toString(); + } catch (Exception e) { + s_logger.debug("failed to create volume", e); + errMsg = e.toString(); + } + } + if (errMsg != null) { + result.setResult(errMsg); + } + + callback.complete(result); + } + + @Override + public void deleteAsync(DataStore dataStore, DataObject data, AsyncCompletionCallback callback) { + DeleteCommand cmd = new DeleteCommand(data.getTO()); + + CommandResult result = new CommandResult(); + try { + EndPoint ep = null; + if (data.getType() == DataObjectType.VOLUME) { + ep = epSelector.select(data, StorageAction.DELETEVOLUME); + } else { + ep = epSelector.select(data); + } + if (ep == null) { + String errMsg = "No remote endpoint to send DeleteCommand, check if host or ssvm is down?"; + s_logger.error(errMsg); + result.setResult(errMsg); + } else { + Answer answer = ep.sendMessage(cmd); + if (answer != null && !answer.getResult()) { + result.setResult(answer.getDetails()); + } + } + } catch (Exception ex) { + s_logger.debug("Unable to destoy volume" + data.getId(), ex); + result.setResult(ex.toString()); + } + callback.complete(result); + } + + @Override + public void copyAsync(DataObject srcdata, DataObject destData, AsyncCompletionCallback callback) { + DataStore store = destData.getDataStore(); + if (store.getRole() == DataStoreRole.Primary) { + if ((srcdata.getType() == DataObjectType.TEMPLATE && destData.getType() == DataObjectType.TEMPLATE)) { + //For CLVM, we need to copy template to primary storage at all, just fake the copy result. + TemplateObjectTO templateObjectTO = new TemplateObjectTO(); + templateObjectTO.setPath(UUID.randomUUID().toString()); + templateObjectTO.setSize(srcdata.getSize()); + templateObjectTO.setPhysicalSize(srcdata.getSize()); + templateObjectTO.setFormat(Storage.ImageFormat.RAW); + CopyCmdAnswer answer = new CopyCmdAnswer(templateObjectTO); + CopyCommandResult result = new CopyCommandResult("", answer); + callback.complete(result); + } else if (srcdata.getType() == DataObjectType.TEMPLATE && destData.getType() == DataObjectType.VOLUME) { + //For CLVM, we need to pass template on secondary storage to hypervisor + String value = configDao.getValue(Config.PrimaryStorageDownloadWait.toString()); + int _primaryStorageDownloadWait = NumbersUtil.parseInt(value, Integer.parseInt(Config.PrimaryStorageDownloadWait.getDefaultValue())); + StoragePoolVO storagePoolVO = primaryStoreDao.findById(store.getId()); + DataStore imageStore = templateManager.getImageStore(storagePoolVO.getDataCenterId(), srcdata.getId()); + DataObject srcData = templateDataFactory.getTemplate(srcdata.getId(), imageStore); + + CopyCommand cmd = new CopyCommand(srcData.getTO(), destData.getTO(), _primaryStorageDownloadWait, true); + EndPoint ep = epSelector.select(srcData, destData); + Answer answer = null; + if (ep == null) { + String errMsg = "No remote endpoint to send command, check if host or ssvm is down?"; + s_logger.error(errMsg); + answer = new Answer(cmd, false, errMsg); + } else { + answer = ep.sendMessage(cmd); + } + CopyCommandResult result = new CopyCommandResult("", answer); + callback.complete(result); + } + } + } + + @Override + public boolean canCopy(DataObject srcData, DataObject destData) { + //BUG fix for CLOUDSTACK-4618 + DataStore store = destData.getDataStore(); + if (store.getRole() == DataStoreRole.Primary && srcData.getType() == DataObjectType.TEMPLATE + && (destData.getType() == DataObjectType.TEMPLATE || destData.getType() == DataObjectType.VOLUME)) { + StoragePoolVO storagePoolVO = primaryStoreDao.findById(store.getId()); + if (storagePoolVO != null && storagePoolVO.getPoolType() == Storage.StoragePoolType.CLVM) { + return true; + } + } + return false; + } + + @Override + public void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback callback) { + CreateCmdResult result = null; + try { + SnapshotObjectTO snapshotTO = (SnapshotObjectTO) snapshot.getTO(); + Object payload = snapshot.getPayload(); + if (payload != null && payload instanceof CreateSnapshotPayload) { + CreateSnapshotPayload snapshotPayload = (CreateSnapshotPayload) payload; + snapshotTO.setQuiescevm(snapshotPayload.getQuiescevm()); + } + + CreateObjectCommand cmd = new CreateObjectCommand(snapshotTO); + EndPoint ep = epSelector.select(snapshot, StorageAction.TAKESNAPSHOT); + Answer answer = null; + + if (ep == null) { + String errMsg = "No remote endpoint to send createObjectCommand, check if host or ssvm is down?"; + s_logger.error(errMsg); + answer = new Answer(cmd, false, errMsg); + } else { + answer = ep.sendMessage(cmd); + } + + result = new CreateCmdResult(null, answer); + if (answer != null && !answer.getResult()) { + result.setResult(answer.getDetails()); + } + + callback.complete(result); + return; + } catch (Exception e) { + s_logger.debug("Failed to take snapshot: " + snapshot.getId(), e); + result = new CreateCmdResult(null, null); + result.setResult(e.toString()); + } + callback.complete(result); + } + + @Override + public void revertSnapshot(SnapshotInfo snapshot, SnapshotInfo snapshotOnPrimaryStore, AsyncCompletionCallback callback) { + SnapshotObjectTO snapshotTO = (SnapshotObjectTO)snapshot.getTO(); + RevertSnapshotCommand cmd = new RevertSnapshotCommand(snapshotTO); + + CommandResult result = new CommandResult(); + try { + EndPoint ep = epSelector.select(snapshotOnPrimaryStore); + if ( ep == null ){ + String errMsg = "No remote endpoint to send RevertSnapshotCommand, check if host or ssvm is down?"; + s_logger.error(errMsg); + result.setResult(errMsg); + } else { + Answer answer = ep.sendMessage(cmd); + if (answer != null && !answer.getResult()) { + result.setResult(answer.getDetails()); + } + } + } catch (Exception ex) { + s_logger.debug("Unable to revert snapshot " + snapshot.getId(), ex); + result.setResult(ex.toString()); + } + callback.complete(result); + } + + @Override + public void resize(DataObject data, AsyncCompletionCallback callback) { + VolumeObject vol = (VolumeObject) data; + StoragePool pool = (StoragePool) data.getDataStore(); + ResizeVolumePayload resizeParameter = (ResizeVolumePayload) vol.getpayload(); + + ResizeVolumeCommand resizeCmd = + new ResizeVolumeCommand(vol.getPath(), new StorageFilerTO(pool), vol.getSize(), resizeParameter.newSize, resizeParameter.shrinkOk, + resizeParameter.instanceName); + CreateCmdResult result = new CreateCmdResult(null, null); + try { + ResizeVolumeAnswer answer = (ResizeVolumeAnswer) storageMgr.sendToPool(pool, resizeParameter.hosts, resizeCmd); + if (answer != null && answer.getResult()) { + long finalSize = answer.getNewSize(); + s_logger.debug("Resize: volume started at size " + vol.getSize() + " and ended at size " + finalSize); + + vol.setSize(finalSize); + vol.update(); + } else if (answer != null) { + result.setResult(answer.getDetails()); + } else { + s_logger.debug("return a null answer, mark it as failed for unknown reason"); + result.setResult("return a null answer, mark it as failed for unknown reason"); + } + + } catch (Exception e) { + s_logger.debug("sending resize command failed", e); + result.setResult(e.toString()); + } + + callback.complete(result); + } +} diff --git a/cosmic-core/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImpl.java b/cosmic-core/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImpl.java new file mode 100644 index 0000000000..5086b50166 --- /dev/null +++ b/cosmic-core/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImpl.java @@ -0,0 +1,540 @@ +/* + * 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 org.apache.cloudstack.storage.datastore.lifecycle; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import javax.inject.Inject; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.CreateStoragePoolCommand; +import com.cloud.agent.api.DeleteStoragePoolCommand; +import com.cloud.agent.api.StoragePoolInfo; +import com.cloud.alert.AlertManager; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.StorageConflictException; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.resource.ResourceManager; +import com.cloud.server.ManagementServer; +import com.cloud.storage.OCFS2Manager; +import com.cloud.storage.Storage.StoragePoolType; +import com.cloud.storage.StorageManager; +import com.cloud.storage.StoragePool; +import com.cloud.storage.StoragePoolAutomation; +import com.cloud.storage.StoragePoolHostVO; +import com.cloud.storage.dao.StoragePoolHostDao; +import com.cloud.storage.dao.StoragePoolWorkDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.user.dao.UserDao; +import com.cloud.utils.NumbersUtil; +import com.cloud.utils.UriUtils; +import com.cloud.utils.db.DB; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VirtualMachineManager; +import com.cloud.vm.dao.ConsoleProxyDao; +import com.cloud.vm.dao.DomainRouterDao; +import com.cloud.vm.dao.SecondaryStorageVmDao; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.VMInstanceDao; + +import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.engine.subsystem.api.storage.HostScope; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreLifeCycle; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreParameters; +import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.storage.volume.datastore.PrimaryDataStoreHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CloudStackPrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLifeCycle { + private static final Logger s_logger = LoggerFactory.getLogger(CloudStackPrimaryDataStoreLifeCycleImpl.class); + @Inject + protected ResourceManager _resourceMgr; + @Inject + PrimaryDataStoreDao primaryDataStoreDao; + @Inject + protected OCFS2Manager _ocfs2Mgr; + @Inject + DataStoreManager dataStoreMgr; + @Inject + AgentManager agentMgr; + @Inject + StorageManager storageMgr; + + @Inject + VolumeDao volumeDao; + @Inject + VMInstanceDao vmDao; + @Inject + ManagementServer server; + @Inject + protected VirtualMachineManager vmMgr; + @Inject + protected SecondaryStorageVmDao _secStrgDao; + @Inject + UserVmDao userVmDao; + @Inject + protected UserDao _userDao; + @Inject + protected DomainRouterDao _domrDao; + @Inject + protected StoragePoolHostDao _storagePoolHostDao; + @Inject + protected AlertManager _alertMgr; + @Inject + protected ConsoleProxyDao _consoleProxyDao; + + @Inject + protected StoragePoolWorkDao _storagePoolWorkDao; + @Inject + PrimaryDataStoreHelper dataStoreHelper; + @Inject + StoragePoolAutomation storagePoolAutmation; + @Inject + protected HostDao _hostDao; + + @SuppressWarnings("unchecked") + @Override + public DataStore initialize(Map dsInfos) { + final Long clusterId = (Long)dsInfos.get("clusterId"); + final Long podId = (Long)dsInfos.get("podId"); + final Long zoneId = (Long)dsInfos.get("zoneId"); + final String url = (String)dsInfos.get("url"); + final String providerName = (String)dsInfos.get("providerName"); + if (clusterId != null && podId == null) { + throw new InvalidParameterValueException("Cluster id requires pod id"); + } + + final PrimaryDataStoreParameters parameters = new PrimaryDataStoreParameters(); + + URI uri = null; + try { + uri = new URI(UriUtils.encodeURIComponent(url)); + if (uri.getScheme() == null) { + throw new InvalidParameterValueException("scheme is null " + url + ", add nfs:// (or cifs://) as a prefix"); + } else if (uri.getScheme().equalsIgnoreCase("nfs")) { + final String uriHost = uri.getHost(); + final String uriPath = uri.getPath(); + if (uriHost == null || uriPath == null || uriHost.trim().isEmpty() || uriPath.trim().isEmpty()) { + throw new InvalidParameterValueException("host or path is null, should be nfs://hostname/path"); + } + } else if (uri.getScheme().equalsIgnoreCase("cifs")) { + // Don't validate against a URI encoded URI. + final URI cifsUri = new URI(url); + final String warnMsg = UriUtils.getCifsUriParametersProblems(cifsUri); + if (warnMsg != null) { + throw new InvalidParameterValueException(warnMsg); + } + } else if (uri.getScheme().equalsIgnoreCase("sharedMountPoint")) { + final String uriPath = uri.getPath(); + if (uriPath == null) { + throw new InvalidParameterValueException("host or path is null, should be sharedmountpoint://localhost/path"); + } + } else if (uri.getScheme().equalsIgnoreCase("rbd")) { + final String uriPath = uri.getPath(); + if (uriPath == null) { + throw new InvalidParameterValueException("host or path is null, should be rbd://hostname/pool"); + } + } else if (uri.getScheme().equalsIgnoreCase("gluster")) { + final String uriHost = uri.getHost(); + final String uriPath = uri.getPath(); + if (uriHost == null || uriPath == null || uriHost.trim().isEmpty() || uriPath.trim().isEmpty()) { + throw new InvalidParameterValueException("host or path is null, should be gluster://hostname/volume"); + } + } + } catch (final URISyntaxException e) { + throw new InvalidParameterValueException(url + " is not a valid uri"); + } + + final String tags = (String)dsInfos.get("tags"); + final Map details = (Map)dsInfos.get("details"); + + parameters.setTags(tags); + parameters.setDetails(details); + + final String scheme = uri.getScheme(); + final String storageHost = uri.getHost(); + String hostPath = null; + try { + hostPath = URLDecoder.decode(uri.getPath(), "UTF-8"); + } catch (final UnsupportedEncodingException e) { + s_logger.error("[ignored] we are on a platform not supporting \"UTF-8\"!?!", e); + } + if (hostPath == null) { // if decoding fails, use getPath() anyway + hostPath = uri.getPath(); + } + final Object localStorage = dsInfos.get("localStorage"); + if (localStorage != null) { + hostPath = hostPath.replaceFirst("/", ""); + hostPath = hostPath.replace("+", " "); + } + final String userInfo = uri.getUserInfo(); + int port = uri.getPort(); + if (s_logger.isDebugEnabled()) { + s_logger.debug("createPool Params @ scheme - " + scheme + " storageHost - " + storageHost + " hostPath - " + hostPath + " port - " + port); + } + if (scheme.equalsIgnoreCase("nfs")) { + if (port == -1) { + port = 2049; + } + parameters.setType(StoragePoolType.NetworkFilesystem); + parameters.setHost(storageHost); + parameters.setPort(port); + parameters.setPath(hostPath); + } else if (scheme.equalsIgnoreCase("cifs")) { + if (port == -1) { + port = 445; + } + + parameters.setType(StoragePoolType.SMB); + parameters.setHost(storageHost); + parameters.setPort(port); + parameters.setPath(hostPath); + } else if (scheme.equalsIgnoreCase("file")) { + if (port == -1) { + port = 0; + } + parameters.setType(StoragePoolType.Filesystem); + parameters.setHost("localhost"); + parameters.setPort(0); + parameters.setPath(hostPath); + } else if (scheme.equalsIgnoreCase("sharedMountPoint")) { + parameters.setType(StoragePoolType.SharedMountPoint); + parameters.setHost(storageHost); + parameters.setPort(0); + parameters.setPath(hostPath); + } else if (scheme.equalsIgnoreCase("clvm")) { + parameters.setType(StoragePoolType.CLVM); + parameters.setHost(storageHost); + parameters.setPort(0); + parameters.setPath(hostPath.replaceFirst("/", "")); + } else if (scheme.equalsIgnoreCase("rbd")) { + if (port == -1) { + port = 6789; + } + parameters.setType(StoragePoolType.RBD); + parameters.setHost(storageHost); + parameters.setPort(port); + parameters.setPath(hostPath.replaceFirst("/", "")); + parameters.setUserInfo(userInfo); + } else if (scheme.equalsIgnoreCase("PreSetup")) { + parameters.setType(StoragePoolType.PreSetup); + parameters.setHost(storageHost); + parameters.setPort(0); + parameters.setPath(hostPath); + } else if (scheme.equalsIgnoreCase("iscsi")) { + final String[] tokens = hostPath.split("/"); + final int lun = NumbersUtil.parseInt(tokens[tokens.length - 1], -1); + if (port == -1) { + port = 3260; + } + if (lun != -1) { + if (clusterId == null) { + throw new IllegalArgumentException("IscsiLUN need to have clusters specified"); + } + parameters.setType(StoragePoolType.IscsiLUN); + parameters.setHost(storageHost); + parameters.setPort(port); + parameters.setPath(hostPath); + } else { + throw new IllegalArgumentException("iSCSI needs to have LUN number"); + } + } else if (scheme.equalsIgnoreCase("iso")) { + if (port == -1) { + port = 2049; + } + parameters.setType(StoragePoolType.ISO); + parameters.setHost(storageHost); + parameters.setPort(port); + parameters.setPath(hostPath); + } else if (scheme.equalsIgnoreCase("ocfs2")) { + port = 7777; + parameters.setType(StoragePoolType.OCFS2); + parameters.setHost("clustered"); + parameters.setPort(port); + parameters.setPath(hostPath); + } else if (scheme.equalsIgnoreCase("gluster")) { + if (port == -1) { + port = 24007; + } + parameters.setType(StoragePoolType.Gluster); + parameters.setHost(storageHost); + parameters.setPort(port); + parameters.setPath(hostPath); + } else { + final StoragePoolType type = Enum.valueOf(StoragePoolType.class, scheme); + + if (type != null) { + parameters.setType(type); + parameters.setHost(storageHost); + parameters.setPort(0); + parameters.setPath(hostPath); + } else { + s_logger.warn("Unable to figure out the scheme for URI: " + uri); + throw new IllegalArgumentException("Unable to figure out the scheme for URI: " + uri); + } + } + + if (localStorage == null) { + final List pools = primaryDataStoreDao.listPoolByHostPath(storageHost, hostPath); + if (!pools.isEmpty() && !scheme.equalsIgnoreCase("sharedmountpoint")) { + final Long oldPodId = pools.get(0).getPodId(); + throw new CloudRuntimeException("Storage pool " + uri + " already in use by another pod (id=" + oldPodId + ")"); + } + } + + final Object existingUuid = dsInfos.get("uuid"); + String uuid = null; + + if (existingUuid != null) { + uuid = (String)existingUuid; + } else if (scheme.equalsIgnoreCase("sharedmountpoint") || scheme.equalsIgnoreCase("clvm")) { + uuid = UUID.randomUUID().toString(); + } else if (scheme.equalsIgnoreCase("PreSetup")) { + uuid = hostPath.replace("/", ""); + } else { + uuid = UUID.nameUUIDFromBytes((storageHost + hostPath).getBytes()).toString(); + } + + final List spHandles = primaryDataStoreDao.findIfDuplicatePoolsExistByUUID(uuid); + if (spHandles != null && spHandles.size() > 0) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Another active pool with the same uuid already exists"); + } + throw new CloudRuntimeException("Another active pool with the same uuid already exists"); + } + + final String poolName = (String)dsInfos.get("name"); + + parameters.setUuid(uuid); + parameters.setZoneId(zoneId); + parameters.setPodId(podId); + parameters.setName(poolName); + parameters.setClusterId(clusterId); + parameters.setProviderName(providerName); + + return dataStoreHelper.createPrimaryDataStore(parameters); + } + + protected boolean createStoragePool(long hostId, StoragePool pool) { + s_logger.debug("creating pool " + pool.getName() + " on host " + hostId); + + if (pool.getPoolType() != StoragePoolType.NetworkFilesystem && pool.getPoolType() != StoragePoolType.Filesystem && + pool.getPoolType() != StoragePoolType.IscsiLUN && pool.getPoolType() != StoragePoolType.Iscsi && + pool.getPoolType() != StoragePoolType.SharedMountPoint && pool.getPoolType() != StoragePoolType.PreSetup && pool.getPoolType() != StoragePoolType.OCFS2 && + pool.getPoolType() != StoragePoolType.RBD && pool.getPoolType() != StoragePoolType.CLVM && pool.getPoolType() != StoragePoolType.SMB && + pool.getPoolType() != StoragePoolType.Gluster) { + s_logger.warn(" Doesn't support storage pool type " + pool.getPoolType()); + return false; + } + final CreateStoragePoolCommand cmd = new CreateStoragePoolCommand(true, pool); + final Answer answer = agentMgr.easySend(hostId, cmd); + if (answer != null && answer.getResult()) { + return true; + } else { + primaryDataStoreDao.expunge(pool.getId()); + String msg = ""; + if (answer != null) { + msg = "Can not create storage pool through host " + hostId + " due to " + answer.getDetails(); + s_logger.warn(msg); + } else { + msg = "Can not create storage pool through host " + hostId + " due to CreateStoragePoolCommand returns null"; + s_logger.warn(msg); + } + throw new CloudRuntimeException(msg); + } + } + + @Override + public boolean attachCluster(DataStore store, ClusterScope scope) { + final PrimaryDataStoreInfo primarystore = (PrimaryDataStoreInfo)store; + // Check if there is host up in this cluster + final List allHosts = + _resourceMgr.listAllUpAndEnabledHosts(Host.Type.Routing, primarystore.getClusterId(), primarystore.getPodId(), primarystore.getDataCenterId()); + if (allHosts.isEmpty()) { + primaryDataStoreDao.expunge(primarystore.getId()); + throw new CloudRuntimeException("No host up to associate a storage pool with in cluster " + primarystore.getClusterId()); + } + + if (primarystore.getPoolType() == StoragePoolType.OCFS2 && !_ocfs2Mgr.prepareNodes(allHosts, primarystore)) { + s_logger.warn("Can not create storage pool " + primarystore + " on cluster " + primarystore.getClusterId()); + primaryDataStoreDao.expunge(primarystore.getId()); + return false; + } + + boolean success = false; + for (final HostVO h : allHosts) { + success = createStoragePool(h.getId(), primarystore); + if (success) { + break; + } + } + + s_logger.debug("In createPool Adding the pool to each of the hosts"); + final List poolHosts = new ArrayList(); + for (final HostVO h : allHosts) { + try { + storageMgr.connectHostToSharedPool(h.getId(), primarystore.getId()); + poolHosts.add(h); + } catch (final StorageConflictException se) { + primaryDataStoreDao.expunge(primarystore.getId()); + throw new CloudRuntimeException("Storage has already been added as local storage"); + } catch (final Exception e) { + s_logger.warn("Unable to establish a connection between " + h + " and " + primarystore, e); + } + } + + if (poolHosts.isEmpty()) { + s_logger.warn("No host can access storage pool " + primarystore + " on cluster " + primarystore.getClusterId()); + primaryDataStoreDao.expunge(primarystore.getId()); + throw new CloudRuntimeException("Failed to access storage pool"); + } + + dataStoreHelper.attachCluster(store); + return true; + } + + @Override + public boolean attachZone(DataStore dataStore, ZoneScope scope, HypervisorType hypervisorType) { + final List hosts = _resourceMgr.listAllUpAndEnabledHostsInOneZoneByHypervisor(hypervisorType, scope.getScopeId()); + s_logger.debug("In createPool. Attaching the pool to each of the hosts."); + final List poolHosts = new ArrayList(); + for (final HostVO host : hosts) { + try { + storageMgr.connectHostToSharedPool(host.getId(), dataStore.getId()); + poolHosts.add(host); + } catch (final StorageConflictException se) { + primaryDataStoreDao.expunge(dataStore.getId()); + throw new CloudRuntimeException("Storage has already been added as local storage to host: " + host.getName()); + } catch (final Exception e) { + s_logger.warn("Unable to establish a connection between " + host + " and " + dataStore, e); + } + } + if (poolHosts.isEmpty()) { + s_logger.warn("No host can access storage pool " + dataStore + " in this zone."); + primaryDataStoreDao.expunge(dataStore.getId()); + throw new CloudRuntimeException("Failed to create storage pool as it is not accessible to hosts."); + } + dataStoreHelper.attachZone(dataStore, hypervisorType); + return true; + } + + @Override + public boolean maintain(DataStore dataStore) { + storagePoolAutmation.maintain(dataStore); + dataStoreHelper.maintain(dataStore); + return true; + } + + @Override + public boolean cancelMaintain(DataStore store) { + dataStoreHelper.cancelMaintain(store); + storagePoolAutmation.cancelMaintain(store); + return true; + } + + @DB + @Override + public boolean deleteDataStore(DataStore store) { + final List hostPoolRecords = _storagePoolHostDao.listByPoolId(store.getId()); + final StoragePool pool = (StoragePool)store; + boolean deleteFlag = false; + // find the hypervisor where the storage is attached to. + HypervisorType hType = null; + if (hostPoolRecords.size() > 0) { + hType = getHypervisorType(hostPoolRecords.get(0).getHostId()); + } + + // Remove the SR associated with the Xenserver + for (final StoragePoolHostVO host : hostPoolRecords) { + final DeleteStoragePoolCommand deleteCmd = new DeleteStoragePoolCommand(pool); + final Answer answer = agentMgr.easySend(host.getHostId(), deleteCmd); + + if (answer != null && answer.getResult()) { + deleteFlag = true; + // if host is KVM hypervisor then send deleteStoragepoolcmd to all the kvm hosts. + if (HypervisorType.KVM != hType) { + break; + } + } else { + if (answer != null) { + s_logger.debug("Failed to delete storage pool: " + answer.getResult()); + } + } + } + + if (!hostPoolRecords.isEmpty() && !deleteFlag) { + throw new CloudRuntimeException("Failed to delete storage pool on host"); + } + + return dataStoreHelper.deletePrimaryDataStore(store); + } + + private HypervisorType getHypervisorType(long hostId) { + final HostVO host = _hostDao.findById(hostId); + if (host != null) { + return host.getHypervisorType(); + } + return HypervisorType.None; + } + + @Override + public boolean attachHost(DataStore store, HostScope scope, StoragePoolInfo existingInfo) { + dataStoreHelper.attachHost(store, scope, existingInfo); + return true; + } + + /* (non-Javadoc) + * @see org.apache.cloudstack.engine.subsystem.api.storage.DataStoreLifeCycle#migrateToObjectStore(org.apache.cloudstack.engine.subsystem.api.storage.DataStore) + */ + @Override + public boolean migrateToObjectStore(DataStore store) { + return false; + } + + @Override + public void updateStoragePool(StoragePool storagePool, Map details) { + } + + @Override + public void enableStoragePool(DataStore dataStore) { + dataStoreHelper.enable(dataStore); + } + + @Override + public void disableStoragePool(DataStore dataStore) { + dataStoreHelper.disable(dataStore); + } +} diff --git a/cosmic-core/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/provider/CloudStackPrimaryDataStoreProviderImpl.java b/cosmic-core/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/provider/CloudStackPrimaryDataStoreProviderImpl.java new file mode 100644 index 0000000000..e40492bbf5 --- /dev/null +++ b/cosmic-core/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/provider/CloudStackPrimaryDataStoreProviderImpl.java @@ -0,0 +1,79 @@ +/* + * 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 org.apache.cloudstack.storage.datastore.provider; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.cloud.utils.component.ComponentContext; + +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreLifeCycle; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProvider; +import org.apache.cloudstack.engine.subsystem.api.storage.HypervisorHostListener; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreProvider; +import org.apache.cloudstack.storage.datastore.driver.CloudStackPrimaryDataStoreDriverImpl; +import org.apache.cloudstack.storage.datastore.lifecycle.CloudStackPrimaryDataStoreLifeCycleImpl; + +public class CloudStackPrimaryDataStoreProviderImpl implements PrimaryDataStoreProvider { + + protected PrimaryDataStoreDriver driver; + protected HypervisorHostListener listener; + protected DataStoreLifeCycle lifecycle; + + CloudStackPrimaryDataStoreProviderImpl() { + + } + + @Override + public String getName() { + return DataStoreProvider.DEFAULT_PRIMARY; + } + + @Override + public DataStoreLifeCycle getDataStoreLifeCycle() { + return lifecycle; + } + + @Override + public boolean configure(Map params) { + lifecycle = ComponentContext.inject(CloudStackPrimaryDataStoreLifeCycleImpl.class); + driver = ComponentContext.inject(CloudStackPrimaryDataStoreDriverImpl.class); + listener = ComponentContext.inject(DefaultHostListener.class); + return true; + } + + @Override + public PrimaryDataStoreDriver getDataStoreDriver() { + return driver; + } + + @Override + public HypervisorHostListener getHostListener() { + return listener; + } + + @Override + public Set getTypes() { + Set types = new HashSet(); + types.add(DataStoreProviderType.PRIMARY); + return types; + } +} diff --git a/cosmic-core/plugins/storage/volume/default/src/main/resources/META-INF/cloudstack/storage-volume-default/module.properties b/cosmic-core/plugins/storage/volume/default/src/main/resources/META-INF/cloudstack/storage-volume-default/module.properties new file mode 100644 index 0000000000..6136988498 --- /dev/null +++ b/cosmic-core/plugins/storage/volume/default/src/main/resources/META-INF/cloudstack/storage-volume-default/module.properties @@ -0,0 +1,18 @@ +# 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. +name=storage-volume-default +parent=storage \ No newline at end of file diff --git a/cosmic-core/plugins/storage/volume/default/src/main/resources/META-INF/cloudstack/storage-volume-default/spring-storage-volume-default-context.xml b/cosmic-core/plugins/storage/volume/default/src/main/resources/META-INF/cloudstack/storage-volume-default/spring-storage-volume-default-context.xml new file mode 100644 index 0000000000..8b50455315 --- /dev/null +++ b/cosmic-core/plugins/storage/volume/default/src/main/resources/META-INF/cloudstack/storage-volume-default/spring-storage-volume-default-context.xml @@ -0,0 +1,35 @@ + + + + + + + + diff --git a/cosmic-core/pom.xml b/cosmic-core/pom.xml new file mode 100644 index 0000000000..f85f3c9a89 --- /dev/null +++ b/cosmic-core/pom.xml @@ -0,0 +1,318 @@ + + 4.0.0 + + + cloud.cosmic + cosmic + 5.1.0.0-SNAPSHOT + + + cosmic-core + pom + Cosmic Core + 5.1.0.0-SNAPSHOT + Cosmic is an IaaS (“Infrastructure as a Service”) cloud orchestration platform. + http://www.cosmic.cloud + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Apache Software Foundation + http://www.apache.org/ + + + + api + nucleo + server + usage + utils + engine + plugins + framework + services + + + + scm:git:git@github.com:MissionCriticalCloud/${project.artifactId}.git + scm:git:git@github.com:MissionCriticalCloud/${project.artifactId}.git + HEAD + + + + + org.mariadb.jdbc + mariadb-java-client + + + + + install + + + + org.apache.maven.plugins + maven-release-plugin + 2.5.3 + + + com.mycila + license-maven-plugin + ${cs.mycila.license.version} + + + cosmic-checklicence + process-classes + + check + + + + + true + true +

    ../LICENSE
    + + XML_STYLE + DOUBLESLASH_STYLE + SEMICOLON_STYLE + + false + + **/target/** + .settings/** + .checkstyle + .project + .classpath + .pmd* + + + + + maven-clean-plugin + + true + + + ${cs.target.dir} + + **/* + + + + dist + + **/* + + + + ${basedir} + + ${cs.target.dir} + dist + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + 1.8 + + + org.apache.rat + apache-rat-plugin + + 0 + false + + CHANGES.md + README.md + INSTALL.md + CONTRIBUTING.md + tools/docker/Dockerfile + tools/docker/supervisord.conf + .idea/ + **/*.log + **/*.patch + **/.classpath + **/.project + **/.idea/** + **/*.iml + **/.settings/** + .metadata/** + .git/** + .gitignore + **/*.crt + **/*.csr + **/*.key + **/authorized_keys + **/*.war + **/*.mar + **/*.jar + **/*.iso + **/*.tgz + **/*.zip + **/target/** + **/.vagrant + **/*.json + build/build.number + services/console-proxy/server/js/jquery.js + debian/compat + debian/control + debian/dirs + debian/rules + debian/source/format + dist/console-proxy/js/jquery.js + scripts/vm/systemvm/id_rsa.cloud + services/console-proxy/server/conf/agent.properties + services/console-proxy/server/conf/environment.properties + services/secondary-storage/conf/agent.properties + services/secondary-storage/conf/environment.properties + test/systemvm/README.md + tools/appliance/.ruby-version + tools/vagrant/systemvm/vagrant.pub + tools/vagrant/systemvm/.ruby-version + tools/appliance/definitions/systemvmtemplate/* + tools/appliance/definitions/systemvm64template/* + tools/appliance/definitions/builtin/* + tools/cli/cloudmonkey.egg-info/* + systemvm/conf/agent.properties + systemvm/conf/environment.properties + systemvm/js/jquery.js + systemvm/patches/debian/systemvm.vmx + systemvm/patches/debian/config/root/.ssh/authorized_keys + systemvm/patches/debian/config/etc/apache2/httpd.conf + systemvm/patches/debian/config/etc/apache2/ports.conf + systemvm/patches/debian/config/etc/apache2/sites-available/default + systemvm/patches/debian/config/etc/apache2/sites-available/default-ssl + systemvm/patches/debian/config/etc/apache2/vhostexample.conf + systemvm/patches/debian/config/etc/dnsmasq.conf.tmpl + systemvm/patches/debian/config/etc/vpcdnsmasq.conf + systemvm/patches/debian/config/etc/ssh/sshd_config + systemvm/patches/debian/config/etc/rsyslog.conf + systemvm/patches/debian/config/etc/logrotate.conf + systemvm/patches/debian/config/etc/logrotate.d/* + systemvm/patches/debian/config/etc/sysctl.conf + systemvm/patches/debian/config/root/redundant_router/keepalived.conf.templ + systemvm/patches/debian/config/root/redundant_router/arping_gateways.sh.templ + systemvm/patches/debian/config/root/redundant_router/conntrackd.conf.templ + systemvm/patches/debian/vpn/etc/ipsec.conf + systemvm/patches/debian/vpn/etc/ppp/options.xl2tpd + systemvm/patches/debian/vpn/etc/xl2tpd/xl2tpd.conf + systemvm/patches/debian/vpn/etc/ipsec.secrets + systemvm/patches/debian/config/etc/haproxy/haproxy.cfg + systemvm/patches/debian/config/etc/cloud-nic.rules + systemvm/patches/debian/config/etc/modprobe.d/aesni_intel + systemvm/patches/debian/config/etc/rc.local + systemvm/patches/debian/config/var/www/html/userdata/.htaccess + systemvm/patches/debian/config/var/www/html/latest/.htaccess + systemvm/patches/debian/vpn/etc/ipsec.d/l2tp.conf + tools/transifex/.tx/config + tools/ngui/static/bootstrap/* + tools/ngui/static/js/lib/* + **/.checkstyle + **/*.md + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.2 + + true + 128m + 512m + -XDignore.symbol.file=true + + + + org.apache.maven.plugins + maven-jar-plugin + 2.5 + + + + true + true + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 1.9.1 + + + remove-old-installers + + remove-project-artifact + + + true + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.10 + + + org.codehaus.mojo + cobertura-maven-plugin + + + html + xml + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.18.1 + + -Djava.security.egd=file:/dev/./urandom -noverify + + + + org.apache.maven.plugins + maven-failsafe-plugin + 2.18.1 + + + + + + + systemvm + + + systemvm + + + + systemvm + + + + developer + + test + developer + tools + + + + diff --git a/cosmic-core/python/bindir/cloud-external-ipallocator.py b/cosmic-core/python/bindir/cloud-external-ipallocator.py new file mode 100755 index 0000000000..f5c103940c --- /dev/null +++ b/cosmic-core/python/bindir/cloud-external-ipallocator.py @@ -0,0 +1,159 @@ +#! /usr/bin/python +# 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. + + + + + +import web +import socket, struct +import cloud_utils +from cloud_utils import Command +urls = ("/ipallocator", "ipallocator") +app = web.application(urls, globals()) + +augtool = Command("augtool") +service = Command("service") +class dhcp: + _instance = None + def __init__(self): + self.availIP=[] + self.router=None + self.netmask=None + self.initialized=False + + options = augtool.match("/files/etc/dnsmasq.conf/dhcp-option").stdout.strip() + for option in options.splitlines(): + if option.find("option:router") != -1: + self.router = option.split("=")[1].strip().split(",")[1] + print self.router + + dhcp_range = augtool.get("/files/etc/dnsmasq.conf/dhcp-range").stdout.strip() + dhcp_start = dhcp_range.split("=")[1].strip().split(",")[0] + dhcp_end = dhcp_range.split("=")[1].strip().split(",")[1] + self.netmask = dhcp_range.split("=")[1].strip().split(",")[2] + print dhcp_start, dhcp_end, self.netmask + + start_ip_num = self.ipToNum(dhcp_start); + end_ip_num = self.ipToNum(dhcp_end) + print start_ip_num, end_ip_num + + for ip in range(start_ip_num, end_ip_num + 1): + self.availIP.append(ip) + print self.availIP[0], self.availIP[len(self.availIP) - 1] + + #load the ip already allocated + self.reloadAllocatedIP() + + def ipToNum(self, ip): + return struct.unpack("!I", socket.inet_aton(ip))[0] + + def numToIp(self, num): + return socket.inet_ntoa(struct.pack('!I', num)) + + def getFreeIP(self): + if len(self.availIP) > 0: + ip = self.numToIp(self.availIP[0]) + self.availIP.remove(self.availIP[0]) + return ip + else: + return None + + def getNetmask(self): + return self.netmask + + def getRouter(self): + return self.router + + def getInstance(): + if not dhcp._instance: + dhcp._instance = dhcp() + return dhcp._instance + getInstance = staticmethod(getInstance) + + def reloadAllocatedIP(self): + dhcp_hosts = augtool.match("/files/etc/dnsmasq.conf/dhcp-host").stdout.strip().splitlines() + + for host in dhcp_hosts: + if host.find("dhcp-host") != -1: + allocatedIP = self.ipToNum(host.split("=")[1].strip().split(",")[1]) + if allocatedIP in self.availIP: + self.availIP.remove(allocatedIP) + + def allocateIP(self, mac): + newIP = self.getFreeIP() + dhcp_host = augtool.match("/files/etc/dnsmasq.conf/dhcp-host").stdout.strip() + cnt = len(dhcp_host.splitlines()) + 1 + script = """set %s %s + save"""%("/files/etc/dnsmasq.conf/dhcp-host[" + str(cnt) + "]", str(mac) + "," + newIP) + augtool < script + #reset dnsmasq + service("dnsmasq", "restart", stdout=None, stderr=None) + return newIP + + def releaseIP(self, ip): + dhcp_host = augtool.match("/files/etc/dnsmasq.conf/dhcp-host").stdout.strip() + path = None + for host in dhcp_host.splitlines(): + if host.find(ip) != -1: + path = host.split("=")[0].strip() + + if path == None: + print "Can't find " + str(ip) + " in conf file" + return None + + print path + script = """rm %s + save"""%(path) + augtool < script + + self.availIP.remove(ip) + + #reset dnsmasq + service("dnsmasq", "restart", stdout=None, stderr=None) + +class ipallocator: + def GET(self): + try: + user_data = web.input() + command = user_data.command + print "Processing: " + command + + dhcpInit = dhcp.getInstance() + + if command == "getIpAddr": + mac = user_data.mac + zone_id = user_data.dc + pod_id = user_data.pod + print mac, zone_id, pod_id + freeIP = dhcpInit.allocateIP(mac) + if not freeIP: + return "0,0,0" + print "Find an available IP: " + freeIP + + return freeIP + "," + dhcpInit.getNetmask() + "," + dhcpInit.getRouter() + elif command == "releaseIpAddr": + ip = user_data.ip + zone_id = user_data.dc + pod_id = user_data.pod + dhcpInit.releaseIP(ip) + except: + return None + +if __name__ == "__main__": + app.run() diff --git a/cosmic-core/python/bindir/cloud-grab-dependent-library-versions b/cosmic-core/python/bindir/cloud-grab-dependent-library-versions new file mode 100755 index 0000000000..d485cb05e3 --- /dev/null +++ b/cosmic-core/python/bindir/cloud-grab-dependent-library-versions @@ -0,0 +1,75 @@ +#!/usr/bin/python +# 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. + +import subprocess + +depLibraries = ['python', 'bzip2', 'gzip', 'unzip', 'openssh-clients', 'nfs-utils', 'wget', 'tomcat6', 'ws-commons-util', 'commons-dbcp', + 'commons-collections', 'commons-httpclient', 'jpackage-utils', 'MySQL-python', 'python-paramiko', 'ipmitool', 'commons-httpclient', 'commons-collections', + 'commons-pool', 'commons-dbcp', 'jakarta-commons-logging', 'java-*-openjdk'] + +def runCmd(cmds): + process = subprocess.Popen(' '.join(cmds), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = process.communicate() + if process.returncode != 0: + raise Exception(stderr) + return stdout + + +def getDependentLibraryInfo(): + def getVersion(res, pkgname): + start = False + for l in res.split('\n'): + if "Installed Packages" in l: + start = True + continue + if not start: continue + + (key, value) = l.split(':', 2) + key = key.strip() + value = value.strip() + if key == 'Name' and "*" not in pkgname and pkgname not in value: + print "Required package name %s doesn't equal to package %s installed"%(pkgname, value) + return 'UNKNOWN' + if 'Version' in key: return value + if 'Description' in key: return 'UNKNOWN' # we hit the end + return 'UNKNOWN' + + libraryMap = {} + for l in depLibraries: + cmd = ['yum', 'info', '"%s"'%l] + try: + result = runCmd(cmd) + version = getVersion(result, l) + libraryMap[l] = version + except Exception, e: + print "When finding %s, encounters %s"%(l, e) + continue + return libraryMap + +def arrangeOutPut(libraryMap): + msg = ['\n\n\nBelow is the checking list of library version that CloudStack depends on:'] + for l in depLibraries: + if libraryMap.has_key(l): + entry = "%-40s: %s"%(l, libraryMap[l]) + else: + entry = "%-40s: %s"%(l, 'UNKNOWN') + msg.append(entry) + print '\n'.join(msg) + +if __name__ == '__main__': + arrangeOutPut(getDependentLibraryInfo()) diff --git a/cosmic-core/python/distro/centos/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in b/cosmic-core/python/distro/centos/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in new file mode 100755 index 0000000000..3921484e54 --- /dev/null +++ b/cosmic-core/python/distro/centos/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in @@ -0,0 +1,96 @@ +#!/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 + +# set environment variables + +SHORTNAME=`basename $0` +PIDFILE=@PIDDIR@/"$SHORTNAME".pid +LOCKFILE=@LOCKDIR@/"$SHORTNAME" +LOGFILE=@IPALOCATORLOG@ +PROGNAME="External IPAllocator" + +unset OPTIONS +[ -r @SYSCONFDIR@/sysconfig/"$SHORTNAME" ] && source @SYSCONFDIR@/sysconfig/"$SHORTNAME" +DAEMONIZE=@BINDIR@/@PACKAGE@-daemonize +PROG=@BINDIR@/@PACKAGE@-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: $SHORTNAME {start|stop|restart|condrestart|status|help}" + RETVAL=3 +esac + +exit $RETVAL + diff --git a/cosmic-core/python/distro/fedora/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in b/cosmic-core/python/distro/fedora/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in new file mode 100755 index 0000000000..23ec8f3cd6 --- /dev/null +++ b/cosmic-core/python/distro/fedora/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in @@ -0,0 +1,96 @@ +#!/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 + +# set environment variables + +SHORTNAME=`basename $0` +PIDFILE=@PIDDIR@/"$SHORTNAME".pid +LOCKFILE=@LOCKDIR@/"$SHORTNAME" +LOGFILE=@AGENTLOG@ +PROGNAME="Cloud Agent" + +unset OPTIONS +[ -r @SYSCONFDIR@/sysconfig/"$SHORTNAME" ] && source @SYSCONFDIR@/sysconfig/"$SHORTNAME" +DAEMONIZE=@BINDIR@/@PACKAGE@-daemonize +PROG=@BINDIR@/@PACKAGE@-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: $SHORTNAME {start|stop|restart|condrestart|status|help}" + RETVAL=3 +esac + +exit $RETVAL + diff --git a/cosmic-core/python/distro/opensuse/SYSCONFDIR/init.d/cloud-ipallocator.in b/cosmic-core/python/distro/opensuse/SYSCONFDIR/init.d/cloud-ipallocator.in new file mode 100755 index 0000000000..51b4f58e17 --- /dev/null +++ b/cosmic-core/python/distro/opensuse/SYSCONFDIR/init.d/cloud-ipallocator.in @@ -0,0 +1,116 @@ +#!/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. + +### BEGIN INIT INFO +# Provides: cloudstack-ipallocator +# Required-Start: $network +# Required-Stop: $network +# Default-Start: 3 4 5 +# Default-Stop: 0 1 2 6 +# description: Cloud IP address allocation +### END INIT INFO + +# WARNING: if this script is changed, then all other initscripts MUST BE changed to match it as well + +. /lib/lsb/init-functions +. /etc/rc.status + +# set environment variables + +SHORTNAME=`basename $0` +PIDFILE=@PIDDIR@/"$SHORTNAME".pid +LOCKFILE=@LOCKDIR@/"$SHORTNAME" +LOGFILE=@AGENTLOG@ +PROGNAME="Cloud Agent" + +unset OPTIONS +[ -r @SYSCONFDIR@/default/"$SHORTNAME" ] && source @SYSCONFDIR@/default/"$SHORTNAME" +DAEMONIZE=@BINDIR@/@PACKAGE@-daemonize +PROG=@BINDIR@/@PACKAGE@-external-ipallocator.py +OPTIONS=8083 + +start() { + log_daemon_msg $"Starting $PROGNAME" "$SHORTNAME" + if [ -s "$PIDFILE" ] && kill -0 $(cat "$PIDFILE") >/dev/null 2>&1; then + log_progress_msg "apparently already running" + log_end_msg 0 + exit 0 + fi + if hostname --fqdn >/dev/null 2>&1 ; then + true + else + log_failure_msg "The host name does not resolve properly to an IP address. Cannot start $PROGNAME" + log_end_msg 1 + exit 1 + fi + + if start-stop-daemon --start --quiet \ + --pidfile "$PIDFILE" \ + --exec "$DAEMONIZE" -- -n "$SHORTNAME" -p "$PIDFILE" -l "$LOGFILE" "$PROG" $OPTIONS + RETVAL=$? + then + rc=0 + sleep 1 + if ! kill -0 $(cat "$PIDFILE") >/dev/null 2>&1; then + log_failure_msg "$PROG failed to start" + rc=1 + fi + else + rc=1 + fi + + if [ $rc -eq 0 ]; then + log_end_msg 0 + else + log_end_msg 1 + rm -f "$PIDFILE" + fi +} + +stop() { + echo -n $"Stopping $PROGNAME" "$SHORTNAME" + start-stop-daemon --stop --quiet --oknodo --pidfile "$PIDFILE" + log_end_msg $? + rm -f "$PIDFILE" +} + + +# See how we were called. +case "$1" in + start) + start + ;; + stop) + stop + ;; + status) + status_of_proc -p "$PIDFILE" "$PROG" "$SHORTNAME" + RETVAL=$? + ;; + restart) + stop + sleep 3 + start + ;; + *) + echo $"Usage: $SHORTNAME {start|stop|restart|status|help}" + RETVAL=3 +esac + +exit $RETVAL + diff --git a/cosmic-core/python/distro/rhel/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in b/cosmic-core/python/distro/rhel/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in new file mode 100644 index 0000000000..23ec8f3cd6 --- /dev/null +++ b/cosmic-core/python/distro/rhel/SYSCONFDIR/rc.d/init.d/cloud-ipallocator.in @@ -0,0 +1,96 @@ +#!/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 + +# set environment variables + +SHORTNAME=`basename $0` +PIDFILE=@PIDDIR@/"$SHORTNAME".pid +LOCKFILE=@LOCKDIR@/"$SHORTNAME" +LOGFILE=@AGENTLOG@ +PROGNAME="Cloud Agent" + +unset OPTIONS +[ -r @SYSCONFDIR@/sysconfig/"$SHORTNAME" ] && source @SYSCONFDIR@/sysconfig/"$SHORTNAME" +DAEMONIZE=@BINDIR@/@PACKAGE@-daemonize +PROG=@BINDIR@/@PACKAGE@-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: $SHORTNAME {start|stop|restart|condrestart|status|help}" + RETVAL=3 +esac + +exit $RETVAL + diff --git a/cosmic-core/python/distro/sles/SYSCONFDIR/init.d/cloud-ipallocator.in b/cosmic-core/python/distro/sles/SYSCONFDIR/init.d/cloud-ipallocator.in new file mode 100755 index 0000000000..51b4f58e17 --- /dev/null +++ b/cosmic-core/python/distro/sles/SYSCONFDIR/init.d/cloud-ipallocator.in @@ -0,0 +1,116 @@ +#!/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. + +### BEGIN INIT INFO +# Provides: cloudstack-ipallocator +# Required-Start: $network +# Required-Stop: $network +# Default-Start: 3 4 5 +# Default-Stop: 0 1 2 6 +# description: Cloud IP address allocation +### END INIT INFO + +# WARNING: if this script is changed, then all other initscripts MUST BE changed to match it as well + +. /lib/lsb/init-functions +. /etc/rc.status + +# set environment variables + +SHORTNAME=`basename $0` +PIDFILE=@PIDDIR@/"$SHORTNAME".pid +LOCKFILE=@LOCKDIR@/"$SHORTNAME" +LOGFILE=@AGENTLOG@ +PROGNAME="Cloud Agent" + +unset OPTIONS +[ -r @SYSCONFDIR@/default/"$SHORTNAME" ] && source @SYSCONFDIR@/default/"$SHORTNAME" +DAEMONIZE=@BINDIR@/@PACKAGE@-daemonize +PROG=@BINDIR@/@PACKAGE@-external-ipallocator.py +OPTIONS=8083 + +start() { + log_daemon_msg $"Starting $PROGNAME" "$SHORTNAME" + if [ -s "$PIDFILE" ] && kill -0 $(cat "$PIDFILE") >/dev/null 2>&1; then + log_progress_msg "apparently already running" + log_end_msg 0 + exit 0 + fi + if hostname --fqdn >/dev/null 2>&1 ; then + true + else + log_failure_msg "The host name does not resolve properly to an IP address. Cannot start $PROGNAME" + log_end_msg 1 + exit 1 + fi + + if start-stop-daemon --start --quiet \ + --pidfile "$PIDFILE" \ + --exec "$DAEMONIZE" -- -n "$SHORTNAME" -p "$PIDFILE" -l "$LOGFILE" "$PROG" $OPTIONS + RETVAL=$? + then + rc=0 + sleep 1 + if ! kill -0 $(cat "$PIDFILE") >/dev/null 2>&1; then + log_failure_msg "$PROG failed to start" + rc=1 + fi + else + rc=1 + fi + + if [ $rc -eq 0 ]; then + log_end_msg 0 + else + log_end_msg 1 + rm -f "$PIDFILE" + fi +} + +stop() { + echo -n $"Stopping $PROGNAME" "$SHORTNAME" + start-stop-daemon --stop --quiet --oknodo --pidfile "$PIDFILE" + log_end_msg $? + rm -f "$PIDFILE" +} + + +# See how we were called. +case "$1" in + start) + start + ;; + stop) + stop + ;; + status) + status_of_proc -p "$PIDFILE" "$PROG" "$SHORTNAME" + RETVAL=$? + ;; + restart) + stop + sleep 3 + start + ;; + *) + echo $"Usage: $SHORTNAME {start|stop|restart|status|help}" + RETVAL=3 +esac + +exit $RETVAL + diff --git a/cosmic-core/python/distro/ubuntu/SYSCONFDIR/init.d/cloud-ipallocator.in b/cosmic-core/python/distro/ubuntu/SYSCONFDIR/init.d/cloud-ipallocator.in new file mode 100755 index 0000000000..e2cb36197a --- /dev/null +++ b/cosmic-core/python/distro/ubuntu/SYSCONFDIR/init.d/cloud-ipallocator.in @@ -0,0 +1,110 @@ +#!/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 + +. /lib/lsb/init-functions +. /etc/default/rcS + +# set environment variables + +SHORTNAME=`basename $0` +PIDFILE=@PIDDIR@/"$SHORTNAME".pid +LOCKFILE=@LOCKDIR@/"$SHORTNAME" +LOGFILE=@AGENTLOG@ +PROGNAME="Cloud Agent" + +unset OPTIONS +[ -r @SYSCONFDIR@/default/"$SHORTNAME" ] && source @SYSCONFDIR@/default/"$SHORTNAME" +DAEMONIZE=@BINDIR@/@PACKAGE@-daemonize +PROG=@BINDIR@/@PACKAGE@-external-ipallocator.py +OPTIONS=8083 + +start() { + log_daemon_msg $"Starting $PROGNAME" "$SHORTNAME" + if [ -s "$PIDFILE" ] && kill -0 $(cat "$PIDFILE") >/dev/null 2>&1; then + log_progress_msg "apparently already running" + log_end_msg 0 + exit 0 + fi + if hostname --fqdn >/dev/null 2>&1 ; then + true + else + log_failure_msg "The host name does not resolve properly to an IP address. Cannot start $PROGNAME" + log_end_msg 1 + exit 1 + fi + + if start-stop-daemon --start --quiet \ + --pidfile "$PIDFILE" \ + --exec "$DAEMONIZE" -- -n "$SHORTNAME" -p "$PIDFILE" -l "$LOGFILE" "$PROG" $OPTIONS + RETVAL=$? + then + rc=0 + sleep 1 + if ! kill -0 $(cat "$PIDFILE") >/dev/null 2>&1; then + log_failure_msg "$PROG failed to start" + rc=1 + fi + else + rc=1 + fi + + if [ $rc -eq 0 ]; then + log_end_msg 0 + else + log_end_msg 1 + rm -f "$PIDFILE" + fi +} + +stop() { + echo -n $"Stopping $PROGNAME" "$SHORTNAME" + start-stop-daemon --stop --quiet --oknodo --pidfile "$PIDFILE" + log_end_msg $? + rm -f "$PIDFILE" +} + + +# See how we were called. +case "$1" in + start) + start + ;; + stop) + stop + ;; + status) + status_of_proc -p "$PIDFILE" "$PROG" "$SHORTNAME" + RETVAL=$? + ;; + restart) + stop + sleep 3 + start + ;; + *) + echo $"Usage: $SHORTNAME {start|stop|restart|status|help}" + RETVAL=3 +esac + +exit $RETVAL + diff --git a/cosmic-core/python/incubation/cloud-web-ipallocator.py b/cosmic-core/python/incubation/cloud-web-ipallocator.py new file mode 100755 index 0000000000..f5c103940c --- /dev/null +++ b/cosmic-core/python/incubation/cloud-web-ipallocator.py @@ -0,0 +1,159 @@ +#! /usr/bin/python +# 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. + + + + + +import web +import socket, struct +import cloud_utils +from cloud_utils import Command +urls = ("/ipallocator", "ipallocator") +app = web.application(urls, globals()) + +augtool = Command("augtool") +service = Command("service") +class dhcp: + _instance = None + def __init__(self): + self.availIP=[] + self.router=None + self.netmask=None + self.initialized=False + + options = augtool.match("/files/etc/dnsmasq.conf/dhcp-option").stdout.strip() + for option in options.splitlines(): + if option.find("option:router") != -1: + self.router = option.split("=")[1].strip().split(",")[1] + print self.router + + dhcp_range = augtool.get("/files/etc/dnsmasq.conf/dhcp-range").stdout.strip() + dhcp_start = dhcp_range.split("=")[1].strip().split(",")[0] + dhcp_end = dhcp_range.split("=")[1].strip().split(",")[1] + self.netmask = dhcp_range.split("=")[1].strip().split(",")[2] + print dhcp_start, dhcp_end, self.netmask + + start_ip_num = self.ipToNum(dhcp_start); + end_ip_num = self.ipToNum(dhcp_end) + print start_ip_num, end_ip_num + + for ip in range(start_ip_num, end_ip_num + 1): + self.availIP.append(ip) + print self.availIP[0], self.availIP[len(self.availIP) - 1] + + #load the ip already allocated + self.reloadAllocatedIP() + + def ipToNum(self, ip): + return struct.unpack("!I", socket.inet_aton(ip))[0] + + def numToIp(self, num): + return socket.inet_ntoa(struct.pack('!I', num)) + + def getFreeIP(self): + if len(self.availIP) > 0: + ip = self.numToIp(self.availIP[0]) + self.availIP.remove(self.availIP[0]) + return ip + else: + return None + + def getNetmask(self): + return self.netmask + + def getRouter(self): + return self.router + + def getInstance(): + if not dhcp._instance: + dhcp._instance = dhcp() + return dhcp._instance + getInstance = staticmethod(getInstance) + + def reloadAllocatedIP(self): + dhcp_hosts = augtool.match("/files/etc/dnsmasq.conf/dhcp-host").stdout.strip().splitlines() + + for host in dhcp_hosts: + if host.find("dhcp-host") != -1: + allocatedIP = self.ipToNum(host.split("=")[1].strip().split(",")[1]) + if allocatedIP in self.availIP: + self.availIP.remove(allocatedIP) + + def allocateIP(self, mac): + newIP = self.getFreeIP() + dhcp_host = augtool.match("/files/etc/dnsmasq.conf/dhcp-host").stdout.strip() + cnt = len(dhcp_host.splitlines()) + 1 + script = """set %s %s + save"""%("/files/etc/dnsmasq.conf/dhcp-host[" + str(cnt) + "]", str(mac) + "," + newIP) + augtool < script + #reset dnsmasq + service("dnsmasq", "restart", stdout=None, stderr=None) + return newIP + + def releaseIP(self, ip): + dhcp_host = augtool.match("/files/etc/dnsmasq.conf/dhcp-host").stdout.strip() + path = None + for host in dhcp_host.splitlines(): + if host.find(ip) != -1: + path = host.split("=")[0].strip() + + if path == None: + print "Can't find " + str(ip) + " in conf file" + return None + + print path + script = """rm %s + save"""%(path) + augtool < script + + self.availIP.remove(ip) + + #reset dnsmasq + service("dnsmasq", "restart", stdout=None, stderr=None) + +class ipallocator: + def GET(self): + try: + user_data = web.input() + command = user_data.command + print "Processing: " + command + + dhcpInit = dhcp.getInstance() + + if command == "getIpAddr": + mac = user_data.mac + zone_id = user_data.dc + pod_id = user_data.pod + print mac, zone_id, pod_id + freeIP = dhcpInit.allocateIP(mac) + if not freeIP: + return "0,0,0" + print "Find an available IP: " + freeIP + + return freeIP + "," + dhcpInit.getNetmask() + "," + dhcpInit.getRouter() + elif command == "releaseIpAddr": + ip = user_data.ip + zone_id = user_data.dc + pod_id = user_data.pod + dhcpInit.releaseIP(ip) + except: + return None + +if __name__ == "__main__": + app.run() diff --git a/cosmic-core/python/lib/cloud_utils.py b/cosmic-core/python/lib/cloud_utils.py new file mode 100644 index 0000000000..43c93c8763 --- /dev/null +++ b/cosmic-core/python/lib/cloud_utils.py @@ -0,0 +1,1205 @@ +#!/usr/bin/env python +# 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. + + + + + +# -*- coding: utf-8 -*- +"""CloudStack Python utility library""" + +import sys, os, subprocess, errno, re, time, glob +import urllib2 +import xml.dom.minidom +import logging +import socket + +# exit() error constants +E_GENERIC= 1 +E_NOKVM = 2 +E_NODEFROUTE = 3 +E_DHCP = 4 +E_NOPERSISTENTNET = 5 +E_NETRECONFIGFAILED = 6 +E_VIRTRECONFIGFAILED = 7 +E_FWRECONFIGFAILED = 8 +E_AGENTRECONFIGFAILED = 9 +E_AGENTFAILEDTOSTART = 10 +E_NOFQDN = 11 +E_SELINUXENABLED = 12 +try: E_USAGE = os.EX_USAGE +except AttributeError: E_USAGE = 64 + +E_NEEDSMANUALINTERVENTION = 13 +E_INTERRUPTED = 14 +E_SETUPFAILED = 15 +E_UNHANDLEDEXCEPTION = 16 +E_MISSINGDEP = 17 + +Unknown = 0 +Fedora = 1 +CentOS = 2 +Ubuntu = 3 +RHEL6 = 4 + +IPV4 = 4 +IPV6 = 6 + +#=================== DISTRIBUTION DETECTION ================= + +if os.path.exists("/etc/fedora-release"): distro = Fedora +elif os.path.exists("/etc/centos-release"): distro = CentOS +elif os.path.exists("/etc/redhat-release"): + version = file("/etc/redhat-release").readline() + if version.find("Red Hat Enterprise Linux Server release 6") != -1: + distro = RHEL6 + elif version.find("CentOS") != -1: + distro = CentOS + else: + distro = CentOS +elif os.path.exists("/etc/legal") and "Ubuntu" in file("/etc/legal").read(-1): distro = Ubuntu +else: distro = Unknown +logFileName=None +# ================== LIBRARY UTILITY CODE============= +def setLogFile(logFile): + global logFileName + logFileName=logFile +def read_properties(propfile): + if not hasattr(propfile,"read"): propfile = file(propfile) + properties = propfile.read().splitlines() + properties = [ s.strip() for s in properties ] + properties = [ s for s in properties if + s and + not s.startswith("#") and + not s.startswith(";") ] + #[ logging.debug("Valid config file line: %s",s) for s in properties ] + proppairs = [ s.split("=",1) for s in properties ] + return dict(proppairs) + +def stderr(msgfmt,*args): + """Print a message to stderr, optionally interpolating the arguments into it""" + msgfmt += "\n" + if logFileName != None: + sys.stderr = open(logFileName, 'a+') + if args: sys.stderr.write(msgfmt%args) + else: sys.stderr.write(msgfmt) + +def exit(errno=E_GENERIC,message=None,*args): + """Exit with an error status code, printing a message to stderr if specified""" + if message: stderr(message,*args) + sys.exit(errno) + +def resolve(host,port): + return [ (x[4][0],len(x[4])+2) for x in socket.getaddrinfo(host,port,socket.AF_UNSPEC,socket.SOCK_STREAM, 0, socket.AI_PASSIVE) ] + +def resolves_to_ipv6(host,port): + return resolve(host,port)[0][1] == IPV6 + +###add this to Python 2.4, patching the subprocess module at runtime +if hasattr(subprocess,"check_call"): + from subprocess import CalledProcessError, check_call +else: + class CalledProcessError(Exception): + def __init__(self, returncode, cmd): + self.returncode = returncode ; self.cmd = cmd + def __str__(self): return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode) + subprocess.CalledProcessError = CalledProcessError + + def check_call(*popenargs, **kwargs): + retcode = subprocess.call(*popenargs, **kwargs) + cmd = kwargs.get("args") + if cmd is None: cmd = popenargs[0] + if retcode: raise subprocess.CalledProcessError(retcode, cmd) + return retcode + subprocess.check_call = check_call + +# python 2.4 does not have this +try: + any = any + all = all +except NameError: + def any(sequence): + for i in sequence: + if i: return True + return False + def all(sequence): + for i in sequence: + if not i: return False + return True + +class Command: + """This class simulates a shell command""" + def __init__(self,name,parent=None): + self.__name = name + self.__parent = parent + def __getattr__(self,name): + if name == "_print": name = "print" + return Command(name,self) + def __call__(self,*args,**kwargs): + cmd = self.__get_recursive_name() + list(args) + #print " ",cmd + kwargs = dict(kwargs) + if "stdout" not in kwargs: kwargs["stdout"] = subprocess.PIPE + if "stderr" not in kwargs: kwargs["stderr"] = subprocess.PIPE + popen = subprocess.Popen(cmd,**kwargs) + m = popen.communicate() + ret = popen.wait() + if ret: + e = CalledProcessError(ret,cmd) + e.stdout,e.stderr = m + raise e + class CommandOutput: + def __init__(self,stdout,stderr): + self.stdout = stdout + self.stderr = stderr + return CommandOutput(*m) + def __lt__(self,other): + cmd = self.__get_recursive_name() + #print " ",cmd,"<",other + popen = subprocess.Popen(cmd,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE) + m = popen.communicate(other) + ret = popen.wait() + if ret: + e = CalledProcessError(ret,cmd) + e.stdout,e.stderr = m + raise e + class CommandOutput: + def __init__(self,stdout,stderr): + self.stdout = stdout + self.stderr = stderr + return CommandOutput(*m) + + def __get_recursive_name(self,sep=None): + m = self + l = [] + while m is not None: + l.append(m.__name) + m = m.__parent + l.reverse() + if sep: return sep.join(l) + else: return l + def __str__(self): + return ''%self.__get_recursive_name(sep=" ") + + def __repr__(self): return self.__str__() + +kvmok = Command("kvm-ok") +getenforce = Command("/usr/sbin/getenforce") +ip = Command("ip") +service = Command("service") +chkconfig = Command("chkconfig") +updatercd = Command("update-rc.d") +ufw = Command("ufw") +iptables = Command("iptables") +iptablessave = Command("iptables-save") +augtool = Command("augtool") +ifconfig = Command("ifconfig") +ifdown = Command("ifdown") +ifup = Command("ifup") +brctl = Command("brctl") +uuidgen = Command("uuidgen") + + +def is_service_running(servicename): + try: + o = service(servicename,"status") + if distro is Ubuntu: + # status in ubuntu does not signal service status via return code + if "start/running" in o.stdout: return True + return False + else: + # retcode 0, service running + return True + except CalledProcessError,e: + # retcode nonzero, service not running + return False + + +def stop_service(servicename,force=False): + # This function is idempotent. N number of calls have the same result as N+1 number of calls. + if is_service_running(servicename) or force: service(servicename,"stop",stdout=None,stderr=None) + + +def disable_service(servicename): + # Stops AND disables the service + stop_service(servicename) + if distro is Ubuntu: + updatercd("-f",servicename,"remove",stdout=None,stderr=None) + else: + chkconfig("--del",servicename,stdout=None,stderr=None) + + +def start_service(servicename,force=False): + # This function is idempotent unless force is True. N number of calls have the same result as N+1 number of calls. + if not is_service_running(servicename) or force: service(servicename,"start",stdout=None,stderr=None) + + +def enable_service(servicename,forcestart=False): + # Stops AND disables the service + if distro is Ubuntu: + updatercd("-f",servicename,"remove",stdout=None,stderr=None) + updatercd("-f",servicename,"start","2","3","4","5",".",stdout=None,stderr=None) + else: + chkconfig("--add",servicename,stdout=None,stderr=None) + chkconfig("--level","345",servicename,"on",stdout=None,stderr=None) + start_service(servicename,force=forcestart) + + +def replace_line(f,startswith,stanza,always_add=False): + lines = [ s.strip() for s in file(f).readlines() ] + newlines = [] + replaced = False + for line in lines: + if line.startswith(startswith): + newlines.append(stanza) + replaced = True + else: newlines.append(line) + if not replaced and always_add: newlines.append(stanza) + newlines = [ s + '\n' for s in newlines ] + file(f,"w").writelines(newlines) + +def replace_or_add_line(f,startswith,stanza): + return replace_line(f,startswith,stanza,always_add=True) + +# ==================================== CHECK FUNCTIONS ========================== + +# If they return without exception, it's okay. If they raise a CheckFailed exception, that means a condition +# (generallly one that needs administrator intervention) was detected. + +class CheckFailed(Exception): pass + +#check function +def check_hostname(): + """If the hostname is a non-fqdn, fail with CalledProcessError. Else return 0.""" + try: check_call(["hostname",'--fqdn']) + except CalledProcessError: + raise CheckFailed("This machine does not have an FQDN (fully-qualified domain name) for a hostname") + +#check function +def check_kvm(): + if distro in (Fedora,CentOS,RHEL6): + if os.path.exists("/dev/kvm"): return True + raise CheckFailed("KVM is not correctly installed on this system, or support for it is not enabled in the BIOS") + else: + try: + kvmok() + return True + except CalledProcessError: + raise CheckFailed("KVM is not correctly installed on this system, or support for it is not enabled in the BIOS") + except OSError,e: + if e.errno is errno.ENOENT: raise CheckFailed("KVM is not correctly installed on this system, or support for it is not enabled in the BIOS") + raise + return True + raise AssertionError, "check_kvm() should have never reached this part" + +def check_cgroups(): + return glob.glob("/*/cpu.shares") + +#check function +def check_selinux(): + if distro not in [Fedora,CentOS,RHEL6]: return # no selinux outside of those + enforcing = False + config_enforcing = False + try: + output = getenforce().stdout.strip() + if "nforcing" in output: + enforcing = True + if any ( [ s.startswith("SELINUX=enforcing") for s in file("/etc/selinux/config").readlines() ] ): + config_enforcing = True + else: + config_enforcing = False + except (IOError,OSError),e: + if e.errno == 2: pass + else: raise CheckFailed("An unknown error (%s) took place while checking for SELinux"%str(e)) + if enforcing: + raise CheckFailed('''SELinux is set to enforcing. There are two options: +1> Set it permissive in /etc/selinux/config, then reboot the machine. +2> Type 'setenforce Permissive' in commandline, after which you can run this program again. + +We strongly suggest you doing the option 1 that makes sure SELinux goes into permissive after system reboot.\n''') + + if config_enforcing: + print "WARNING: We detected that your SELinux is not configured in permissive. to make sure cloudstack won't block by \ +SELinux after system reboot, we strongly suggest you setting it in permissive in /etc/selinux/config, then reboot the machine." + + +def preflight_checks(do_check_kvm=True): + if distro is Ubuntu: + preflight_checks = [ + (check_hostname,"Checking hostname"), + ] + else: + preflight_checks = [ + (check_hostname,"Checking hostname"), + (check_selinux,"Checking if SELinux is disabled"), + ] + #preflight_checks.append( (check_cgroups,"Checking if the control groups /cgroup filesystem is mounted") ) + if do_check_kvm: preflight_checks.append( (check_kvm,"Checking for KVM") ) + return preflight_checks + + +# ========================== CONFIGURATION TASKS ================================ + +# A Task is a function that runs within the context of its run() function that runs the function execute(), which does several things, reporting back to the caller as it goes with the use of yield +# the done() method ought to return true if the task has run in the past +# the execute() method must implement the configuration act itself +# run() wraps the output of execute() within a Starting taskname and a Completed taskname message +# tasks have a name + +class TaskFailed(Exception): pass + #def __init__(self,code,msg): + #Exception.__init__(self,msg) + #self.code = code + +class ConfigTask: + name = "generic config task" + autoMode=False + def __init__(self): pass + def done(self): + """Returns true if the config task has already been done in the past, false if it hasn't""" + return False + def execute(self): + """Executes the configuration task. Must not be run if test() returned true. + Must yield strings that describe the steps in the task. + Raises TaskFailed if the task failed at some step. + """ + def run (self): + stderr("Starting %s"%self.name) + it = self.execute() + if not it: + pass # not a yielding iterable + else: + for msg in it: stderr(msg) + stderr("Completed %s"%self.name) + def setAutoMode(self, autoMode): + self.autoMode = autoMode + def isAutoMode(self): + return self.autoMode + + +# ============== these are some configuration tasks ================== + +class SetupNetworking(ConfigTask): + name = "network setup" + def __init__(self,brname, pubNic, prvNic): + ConfigTask.__init__(self) + self.brname = brname + self.pubNic = pubNic + self.prvNic = prvNic + self.runtime_state_changed = False + self.was_nm_service_running = None + self.was_net_service_running = None + if distro in (Fedora, CentOS, RHEL6): + self.nmservice = 'NetworkManager' + self.netservice = 'network' + else: + self.nmservice = 'network-manager' + self.netservice = 'networking' + + + def done(self): + try: + alreadysetup = False + if distro in (Fedora,CentOS, RHEL6): + if self.pubNic != None: + alreadysetup = alreadysetup or augtool._print("/files/etc/sysconfig/network-scripts/ifcfg-%s"%self.pubNic).stdout.strip() + if self.prvNic != None: + alreadysetup = alreadysetup or augtool._print("/files/etc/sysconfig/network-scripts/ifcfg-%s"%self.prvNic).stdout.strip() + if not alreadysetup: + alreadysetup = augtool._print("/files/etc/sysconfig/network-scripts/ifcfg-%s"%self.brname).stdout.strip() + + else: + if self.pubNic != None: + alreadysetup = alreadysetup or augtool._print("/files/etc/network/interfaces/iface",self.pubNic).stdout.strip() + if self.prvNic != None: + alreadysetup = alreadysetup or augtool._print("/files/etc/network/interfaces/iface",self.prvNic).stdout.strip() + if not alreadysetup: + alreadysetup = augtool.match("/files/etc/network/interfaces/iface",self.brname).stdout.strip() + return alreadysetup + except OSError,e: + if e.errno is 2: raise TaskFailed("augtool has not been properly installed on this system") + raise + + def restore_state(self): + if not self.runtime_state_changed: return + + try: + o = ifconfig(self.brname) + bridge_exists = True + except CalledProcessError,e: + print e.stdout + e.stderr + bridge_exists = False + + if bridge_exists: + ifconfig(self.brname,"0.0.0.0") + if hasattr(self,"old_net_device"): + ifdown(self.old_net_device) + ifup(self.old_net_device) + try: ifdown(self.brname) + except CalledProcessError: pass + try: ifconfig(self.brname,"down") + except CalledProcessError: pass + try: brctl("delbr",self.brname) + except CalledProcessError: pass + try: ifdown("--force",self.brname) + except CalledProcessError: pass + + + if self.was_net_service_running is None: + # we do nothing + pass + elif self.was_net_service_running == False: + stop_service(self.netservice,force=True) + time.sleep(1) + else: + # we altered service configuration + stop_service(self.netservice,force=True) + time.sleep(1) + try: start_service(self.netservice,force=True) + except CalledProcessError,e: + if e.returncode == 1: pass + else: raise + time.sleep(1) + + if self.was_nm_service_running is None: + # we do nothing + pass + elif self.was_nm_service_running == False: + stop_service(self.nmservice,force=True) + time.sleep(1) + else: + # we altered service configuration + stop_service(self.nmservice,force=True) + time.sleep(1) + start_service(self.nmservice,force=True) + time.sleep(1) + + self.runtime_state_changed = False + + def execute(self): + yield "Determining default route" + routes = ip.route().stdout.splitlines() + defaultroute = [ x for x in routes if x.startswith("default") ] + if not defaultroute: raise TaskFailed("Your network configuration does not have a default route") + + dev = defaultroute[0].split()[4] + yield "Default route assigned to device %s"%dev + + self.old_net_device = dev + + if distro in (Fedora, CentOS, RHEL6): + inconfigfile = "/".join(augtool.match("/files/etc/sysconfig/network-scripts/*/DEVICE",dev).stdout.strip().split("/")[:-1]) + if not inconfigfile: raise TaskFailed("Device %s has not been set up in /etc/sysconfig/network-scripts"%dev) + pathtoconfigfile = inconfigfile[6:] + + if distro in (Fedora, CentOS, RHEL6): + automatic = augtool.match("%s/ONBOOT"%inconfigfile,"yes").stdout.strip() + else: + automatic = augtool.match("/files/etc/network/interfaces/auto/*/",dev).stdout.strip() + if not automatic: + if distro is Fedora: raise TaskFailed("Device %s has not been set up in %s as automatic on boot"%dev,pathtoconfigfile) + else: raise TaskFailed("Device %s has not been set up in /etc/network/interfaces as automatic on boot"%dev) + + if distro not in (Fedora , CentOS, RHEL6): + inconfigfile = augtool.match("/files/etc/network/interfaces/iface",dev).stdout.strip() + if not inconfigfile: raise TaskFailed("Device %s has not been set up in /etc/network/interfaces"%dev) + + if distro in (Fedora, CentOS, RHEL6): + isstatic = augtool.match(inconfigfile + "/BOOTPROTO","none").stdout.strip() + if not isstatic: isstatic = augtool.match(inconfigfile + "/BOOTPROTO","static").stdout.strip() + else: + isstatic = augtool.match(inconfigfile + "/method","static").stdout.strip() + if not isstatic: + if distro in (Fedora, CentOS, RHEL6): raise TaskFailed("Device %s has not been set up as a static device in %s"%(dev,pathtoconfigfile)) + else: raise TaskFailed("Device %s has not been set up as a static device in /etc/network/interfaces"%dev) + + if is_service_running(self.nmservice): + self.was_nm_service_running = True + yield "Stopping NetworkManager to avoid automatic network reconfiguration" + disable_service(self.nmservice) + else: + self.was_nm_service_running = False + + if is_service_running(self.netservice): + self.was_net_service_running = True + else: + self.was_net_service_running = False + + yield "Creating Cloud bridging device and making device %s member of this bridge"%dev + + if distro in (Fedora, CentOS, RHEL6): + ifcfgtext = file(pathtoconfigfile).read() + newf = "/etc/sysconfig/network-scripts/ifcfg-%s"%self.brname + #def restore(): + #try: os.unlink(newf) + #except OSError,e: + #if errno == 2: pass + #raise + #try: file(pathtoconfigfile,"w").write(ifcfgtext) + #except OSError,e: raise + + f = file(newf,"w") ; f.write(ifcfgtext) ; f.flush() ; f.close() + innewconfigfile = "/files" + newf + + script = """set %s/DEVICE %s +set %s/NAME %s +set %s/BRIDGE_PORTS %s +set %s/TYPE Bridge +rm %s/HWADDR +rm %s/UUID +rm %s/HWADDR +rm %s/IPADDR +rm %s/DEFROUTE +rm %s/NETMASK +rm %s/GATEWAY +rm %s/BROADCAST +rm %s/NETWORK +set %s/BRIDGE %s +save"""%(innewconfigfile,self.brname,innewconfigfile,self.brname,innewconfigfile,dev, + innewconfigfile,innewconfigfile,innewconfigfile,innewconfigfile, + inconfigfile,inconfigfile,inconfigfile,inconfigfile,inconfigfile,inconfigfile, + inconfigfile,self.brname) + + yield "Executing the following reconfiguration script:\n%s"%script + + try: + returned = augtool < script + if "Saved 2 file" not in returned.stdout: + print returned.stdout + returned.stderr + #restore() + raise TaskFailed("Network reconfiguration failed.") + else: + yield "Network reconfiguration complete" + except CalledProcessError,e: + #restore() + print e.stdout + e.stderr + raise TaskFailed("Network reconfiguration failed") + else: # Not fedora + backup = file("/etc/network/interfaces").read(-1) + #restore = lambda: file("/etc/network/interfaces","w").write(backup) + + script = """set %s %s +set %s %s +set %s/bridge_ports %s +save"""%(automatic,self.brname,inconfigfile,self.brname,inconfigfile,dev) + + yield "Executing the following reconfiguration script:\n%s"%script + + try: + returned = augtool < script + if "Saved 1 file" not in returned.stdout: + #restore() + raise TaskFailed("Network reconfiguration failed.") + else: + yield "Network reconfiguration complete" + except CalledProcessError,e: + #restore() + print e.stdout + e.stderr + raise TaskFailed("Network reconfiguration failed") + + yield "We are going to restart network services now, to make the network changes take effect. Hit ENTER when you are ready." + if self.isAutoMode(): pass + else: + raw_input() + + # if we reach here, then if something goes wrong we should attempt to revert the runinng state + # if not, then no point + self.runtime_state_changed = True + + yield "Enabling and restarting non-NetworkManager networking" + if distro is Ubuntu: ifup(self.brname,stdout=None,stderr=None) + stop_service(self.netservice) + try: enable_service(self.netservice,forcestart=True) + except CalledProcessError,e: + if e.returncode == 1: pass + else: raise + + yield "Verifying that the bridge is up" + try: + o = ifconfig(self.brname) + except CalledProcessError,e: + print e.stdout + e.stderr + raise TaskFailed("The bridge could not be set up properly") + + yield "Networking restart done" + + +class SetupCgConfig(ConfigTask): + name = "control groups configuration" + + def done(self): + + try: + return "group virt" in file("/etc/cgconfig.conf","r").read(-1) + except IOError,e: + if e.errno is 2: raise TaskFailed("cgconfig has not been properly installed on this system") + raise + + def execute(self): + cgconfig = file("/etc/cgconfig.conf","r").read(-1) + cgconfig = cgconfig + """ +group virt { + cpu { + cpu.shares = 9216; + } +} +""" + file("/etc/cgconfig.conf","w").write(cgconfig) + + stop_service("cgconfig") + enable_service("cgconfig",forcestart=True) + + +class SetupCgRules(ConfigTask): + name = "control group rules setup" + cfgline = "root:/usr/sbin/libvirtd cpu virt/" + + def done(self): + try: + return self.cfgline in file("/etc/cgrules.conf","r").read(-1) + except IOError,e: + if e.errno is 2: raise TaskFailed("cgrulesd has not been properly installed on this system") + raise + + def execute(self): + cgrules = file("/etc/cgrules.conf","r").read(-1) + cgrules = cgrules + "\n" + self.cfgline + "\n" + file("/etc/cgrules.conf","w").write(cgrules) + + stop_service("cgred") + enable_service("cgred") + + +class SetupSecurityDriver(ConfigTask): + name = "security driver setup" + cfgline = "security_driver = \"none\"" + filename = "/etc/libvirt/qemu.conf" + + def done(self): + try: + return self.cfgline in file(self.filename,"r").read(-1) + except IOError,e: + if e.errno is 2: raise TaskFailed("qemu has not been properly installed on this system") + raise + + def execute(self): + libvirtqemu = file(self.filename,"r").read(-1) + libvirtqemu = libvirtqemu + "\n" + self.cfgline + "\n" + file("/etc/libvirt/qemu.conf","w").write(libvirtqemu) + + +class SetupLibvirt(ConfigTask): + name = "libvirt setup" + cfgline = "export CGROUP_DAEMON='cpu:/virt'" + def done(self): + try: + if distro in (Fedora,CentOS, RHEL6): libvirtfile = "/etc/sysconfig/libvirtd" + elif distro is Ubuntu: libvirtfile = "/etc/default/libvirt-bin" + else: raise AssertionError, "We should not reach this" + return self.cfgline in file(libvirtfile,"r").read(-1) + except IOError,e: + if e.errno is 2: raise TaskFailed("libvirt has not been properly installed on this system") + raise + + def execute(self): + if distro in (Fedora,CentOS, RHEL6): libvirtfile = "/etc/sysconfig/libvirtd" + elif distro is Ubuntu: libvirtfile = "/etc/default/libvirt-bin" + else: raise AssertionError, "We should not reach this" + libvirtbin = file(libvirtfile,"r").read(-1) + libvirtbin = libvirtbin + "\n" + self.cfgline + "\n" + file(libvirtfile,"w").write(libvirtbin) + + if distro in (CentOS, Fedora, RHEL6): svc = "libvirtd" + else: svc = "libvirt-bin" + stop_service(svc) + enable_service(svc) + +class SetupLiveMigration(ConfigTask): + name = "live migration setup" + stanzas = ( + "listen_tcp=1", + 'tcp_port="16509"', + 'auth_tcp="none"', + "listen_tls=0", + ) + + def done(self): + try: + lines = [ s.strip() for s in file("/etc/libvirt/libvirtd.conf").readlines() ] + if all( [ stanza in lines for stanza in self.stanzas ] ): return True + except IOError,e: + if e.errno is 2: raise TaskFailed("libvirt has not been properly installed on this system") + raise + + def execute(self): + + for stanza in self.stanzas: + startswith = stanza.split("=")[0] + '=' + replace_or_add_line("/etc/libvirt/libvirtd.conf",startswith,stanza) + + if distro in (Fedora, RHEL6): + replace_or_add_line("/etc/sysconfig/libvirtd","LIBVIRTD_ARGS=","LIBVIRTD_ARGS=-l") + + elif distro is Ubuntu: + if os.path.exists("/etc/init/libvirt-bin.conf"): + replace_line("/etc/init/libvirt-bin.conf", "exec /usr/sbin/libvirtd","exec /usr/sbin/libvirtd -d -l") + else: + replace_or_add_line("/etc/default/libvirt-bin","libvirtd_opts=","libvirtd_opts='-l -d'") + + else: + raise AssertionError("Unsupported distribution") + + if distro in (CentOS, Fedora, RHEL6): svc = "libvirtd" + else: svc = "libvirt-bin" + stop_service(svc) + enable_service(svc) + + +class SetupRequiredServices(ConfigTask): + name = "required services setup" + + def done(self): + if distro in (Fedora, RHEL6): nfsrelated = "rpcbind nfslock" + elif distro is CentOS: nfsrelated = "portmap nfslock" + else: return True + return all( [ is_service_running(svc) for svc in nfsrelated.split() ] ) + + def execute(self): + + if distro in (Fedora, RHEL6): nfsrelated = "rpcbind nfslock" + elif distro is CentOS: nfsrelated = "portmap nfslock" + else: raise AssertionError("Unsupported distribution") + + for svc in nfsrelated.split(): enable_service(svc) + + +class SetupFirewall(ConfigTask): + name = "firewall setup" + + def done(self): + + if distro in (Fedora, CentOS,RHEL6): + if not os.path.exists("/etc/sysconfig/iptables"): return True + if ":on" not in chkconfig("--list","iptables").stdout: return True + else: + if "Status: active" not in ufw.status().stdout: return True + if not os.path.exists("/etc/ufw/before.rules"): return True + rule = "-p tcp -m tcp --dport 16509 -j ACCEPT" + if rule in iptablessave().stdout: return True + return False + + def execute(self): + ports = "22 1798 16509".split() + if distro in (Fedora , CentOS, RHEL6): + for p in ports: iptables("-I","INPUT","1","-p","tcp","--dport",p,'-j','ACCEPT') + o = service.iptables.save() ; print o.stdout + o.stderr + else: + for p in ports: ufw.allow(p) + + +class SetupFirewall2(ConfigTask): + # this closes bug 4371 + name = "additional firewall setup" + def __init__(self,brname): + ConfigTask.__init__(self) + self.brname = brname + + def done(self): + + if distro in (Fedora, CentOS, RHEL6): + if not os.path.exists("/etc/sysconfig/iptables"): return True + if ":on" not in chkconfig("--list","iptables").stdout: return True + return False + else: + if "Status: active" not in ufw.status().stdout: return True + if not os.path.exists("/etc/ufw/before.rules"): return True + return False + + def execute(self): + + yield "Permitting traffic in the bridge interface, migration port and for VNC ports" + + if distro in (Fedora , CentOS, RHEL6): + + for rule in ( + "-I INPUT 1 -p tcp --dport 5900:6100 -j ACCEPT", + "-I INPUT 1 -p tcp --dport 49152:49216 -j ACCEPT", + ): + args = rule.split() + o = iptables(*args) + service.iptables.save(stdout=None,stderr=None) + + else: + + ufw.allow.proto.tcp("from","any","to","any","port","5900:6100") + ufw.allow.proto.tcp("from","any","to","any","port","49152:49216") + + stop_service("ufw") + start_service("ufw") + + +# Tasks according to distribution -- at some point we will split them in separate modules + +def config_tasks(brname, pubNic, prvNic): + if distro is CentOS: + config_tasks = ( + SetupNetworking(brname, pubNic, prvNic), + SetupLibvirt(), + SetupRequiredServices(), + SetupFirewall(), + SetupFirewall2(brname), + ) + elif distro in (Ubuntu,Fedora, RHEL6): + config_tasks = ( + SetupNetworking(brname, pubNic, prvNic), + SetupCgConfig(), + SetupCgRules(), + SetupSecurityDriver(), + SetupLibvirt(), + SetupLiveMigration(), + SetupRequiredServices(), + SetupFirewall(), + SetupFirewall2(brname), + ) + else: + raise AssertionError("Unknown distribution") + return config_tasks + + +def backup_etc(targetdir): + if not targetdir.endswith("/"): targetdir += "/" + check_call( ["mkdir","-p",targetdir] ) + rsynccall = ["rsync","-ax","--delete"] + ["/etc/",targetdir] + check_call( rsynccall ) +def restore_etc(targetdir): + if not targetdir.endswith("/"): targetdir += "/" + rsynccall = ["rsync","-ax","--delete"] + [targetdir,"/etc/"] + check_call( rsynccall ) +def remove_backup(targetdir): + check_call( ["rm","-rf",targetdir] ) + +def list_zonespods(host): + text = urllib2.urlopen('http://%s:8096/client/api?command=listPods'%host).read(-1) + dom = xml.dom.minidom.parseString(text) + x = [ (zonename,podname) + for pod in dom.childNodes[0].childNodes + for podname in [ x.childNodes[0].wholeText for x in pod.childNodes if x.tagName == "name" ] + for zonename in [ x.childNodes[0].wholeText for x in pod.childNodes if x.tagName == "zonename" ] + ] + return x + +def prompt_for_hostpods(zonespods): + """Ask user to select one from those zonespods + Returns (zone,pod) or None if the user made the default selection.""" + while True: + stderr("Type the number of the zone and pod combination this host belongs to (hit ENTER to skip this step)") + print " N) ZONE, POD" + print "================" + for n,(z,p) in enumerate(zonespods): + print "%3d) %s, %s"%(n,z,p) + print "================" + print "> ", + zoneandpod = raw_input().strip() + + if not zoneandpod: + # we go with default, do not touch anything, just break + return None + + try: + # if parsing fails as an int, just vomit and retry + zoneandpod = int(zoneandpod) + if zoneandpod >= len(zonespods) or zoneandpod < 0: raise ValueError, "%s out of bounds"%zoneandpod + except ValueError,e: + stderr(str(e)) + continue # re-ask + + # oh yeah, the int represents an valid zone and pod index in the array + return zonespods[zoneandpod] + +# this configures the agent + +def device_exist(devName): + try: + alreadysetup = False + if distro in (Fedora,CentOS, RHEL6): + alreadysetup = augtool._print("/files/etc/sysconfig/network-scripts/ifcfg-%s"%devName).stdout.strip() + else: + alreadysetup = augtool.match("/files/etc/network/interfaces/iface",devName).stdout.strip() + return alreadysetup + except OSError,e: + return False + +def setup_agent_config(configfile, host, zone, pod, cluster, guid, pubNic, prvNic): + stderr("Examining Agent configuration") + fn = configfile + text = file(fn).read(-1) + lines = [ s.strip() for s in text.splitlines() ] + confopts = dict([ m.split("=",1) for m in lines if "=" in m and not m.startswith("#") ]) + confposes = dict([ (m.split("=",1)[0],n) for n,m in enumerate(lines) if "=" in m and not m.startswith("#") ]) + + if guid != None: + confopts['guid'] = guid + else: + if not "guid" in confopts: + stderr("Generating GUID for this Agent") + confopts['guid'] = uuidgen().stdout.strip() + + if host == None: + try: host = confopts["host"] + except KeyError: host = "localhost" + stderr("Please enter the host name of the management server that this agent will connect to: (just hit ENTER to go with %s)",host) + print "> ", + newhost = raw_input().strip() + if newhost: host = newhost + + confopts["host"] = host + + if pubNic != None and device_exist(pubNic): + confopts["public.network.device"] = pubNic + if prvNic == None or not device_exist(prvNic): + confopts["private.network.device"] = pubNic + + if prvNic != None and device_exist(prvNic): + confopts["private.network.device"] = prvNic + if pubNic == None or not device_exist(pubNic): + confopts["public.network.device"] = prvNic + + stderr("Querying %s for zones and pods",host) + + try: + if zone == None or pod == None: + x = list_zonespods(confopts['host']) + zoneandpod = prompt_for_hostpods(x) + if zoneandpod: + confopts["zone"],confopts["pod"] = zoneandpod + stderr("You selected zone %s pod %s",confopts["zone"],confopts["pod"]) + else: + stderr("Skipped -- using the previous zone %s pod %s",confopts["zone"],confopts["pod"]) + else: + confopts["zone"] = zone + confopts["pod"] = pod + confopts["cluster"] = cluster + except (urllib2.URLError,urllib2.HTTPError),e: + stderr("Query failed: %s. Defaulting to zone %s pod %s",str(e),confopts["zone"],confopts["pod"]) + + for opt,val in confopts.items(): + line = "=".join([opt,val]) + if opt not in confposes: lines.append(line) + else: lines[confposes[opt]] = line + + text = "\n".join(lines) + file(fn,"w").write(text) + +def setup_consoleproxy_config(configfile, host, zone, pod): + stderr("Examining Console Proxy configuration") + fn = configfile + text = file(fn).read(-1) + lines = [ s.strip() for s in text.splitlines() ] + confopts = dict([ m.split("=",1) for m in lines if "=" in m and not m.startswith("#") ]) + confposes = dict([ (m.split("=",1)[0],n) for n,m in enumerate(lines) if "=" in m and not m.startswith("#") ]) + + if not "guid" in confopts: + stderr("Generating GUID for this Console Proxy") + confopts['guid'] = uuidgen().stdout.strip() + + if host == None: + try: host = confopts["host"] + except KeyError: host = "localhost" + stderr("Please enter the host name of the management server that this console-proxy will connect to: (just hit ENTER to go with %s)",host) + print "> ", + newhost = raw_input().strip() + if newhost: host = newhost + confopts["host"] = host + + stderr("Querying %s for zones and pods",host) + + try: + if zone == None or pod == None: + x = list_zonespods(confopts['host']) + zoneandpod = prompt_for_hostpods(x) + if zoneandpod: + confopts["zone"],confopts["pod"] = zoneandpod + stderr("You selected zone %s pod %s",confopts["zone"],confopts["pod"]) + else: + stderr("Skipped -- using the previous zone %s pod %s",confopts["zone"],confopts["pod"]) + else: + confopts["zone"] = zone + confopts["pod"] = pod + except (urllib2.URLError,urllib2.HTTPError),e: + stderr("Query failed: %s. Defaulting to zone %s pod %s",str(e),confopts["zone"],confopts["pod"]) + + for opt,val in confopts.items(): + line = "=".join([opt,val]) + if opt not in confposes: lines.append(line) + else: lines[confposes[opt]] = line + + text = "\n".join(lines) + file(fn,"w").write(text) + +# =========================== DATABASE MIGRATION SUPPORT CODE =================== + +# Migrator, Migratee and Evolvers -- this is the generic infrastructure. + + +class MigratorException(Exception): pass +class NoMigrationPath(MigratorException): pass +class NoMigrator(MigratorException): pass + +INITIAL_LEVEL = '-' + +class Migrator: + """Migrator class. + + The migrator gets a list of Python objects, and discovers MigrationSteps in it. It then sorts the steps into a chain, based on the attributes from_level and to_level in each one of the steps. + + When the migrator's run(context) is called, the chain of steps is applied sequentially on the context supplied to run(), in the order of the chain of steps found at discovery time. See the documentation for the MigrationStep class for information on how that happens. + """ + + def __init__(self,evolver_source): + self.discover_evolvers(evolver_source) + self.sort_evolvers() + + def discover_evolvers(self,source): + self.evolvers = [] + for val in source: + if hasattr(val,"from_level") and hasattr(val,"to_level") and val.to_level: + self.evolvers.append(val) + + def sort_evolvers(self): + new = [] + while self.evolvers: + if not new: + try: idx= [ i for i,s in enumerate(self.evolvers) + if s.from_level == INITIAL_LEVEL ][0] # initial evolver + except IndexError,e: + raise IndexError, "no initial evolver (from_level is None) could be found" + else: + try: idx= [ i for i,s in enumerate(self.evolvers) + if new[-1].to_level == s.from_level ][0] + except IndexError,e: + raise IndexError, "no evolver could be found to evolve from level %s"%new[-1].to_level + new.append(self.evolvers.pop(idx)) + self.evolvers = new + + def get_evolver_chain(self): + return [ (s.from_level, s.to_level, s) for s in self.evolvers ] + + def get_evolver_by_starting_level(self,level): + try: return [ s for s in self.evolvers if s.from_level == level][0] + except IndexError: raise NoMigrator, "No evolver knows how to evolve the database from schema level %r"%level + + def get_evolver_by_ending_level(self,level): + try: return [ s for s in self.evolvers if s.to_level == level][0] + except IndexError: raise NoMigrator, "No evolver knows how to evolve the database to schema level %r"%level + + def run(self, context, dryrun = False, starting_level = None, ending_level = None): + """Runs each one of the steps in sequence, passing the migration context to each. At the end of the process, context.commit() is called to save the changes, or context.rollback() is called if dryrun = True. + + If starting_level is not specified, then the context.get_schema_level() is used to find out at what level the context is at. Then starting_level is set to that. + + If ending_level is not specified, then the evolvers will run till the end of the chain.""" + + assert dryrun is False # NOT IMPLEMENTED, prolly gonna implement by asking the context itself to remember its state + + starting_level = starting_level or context.get_schema_level() or self.evolvers[0].from_level + ending_level = ending_level or self.evolvers[-1].to_level + + evolution_path = self.evolvers + idx = evolution_path.index(self.get_evolver_by_starting_level(starting_level)) + evolution_path = evolution_path[idx:] + try: idx = evolution_path.index(self.get_evolver_by_ending_level(ending_level)) + except ValueError: + raise NoEvolutionPath, "No evolution path from schema level %r to schema level %r" % \ + (starting_level,ending_level) + evolution_path = evolution_path[:idx+1] + + logging.info("Starting migration on %s"%context) + + for ec in evolution_path: + assert ec.from_level == context.get_schema_level() + evolver = ec(context=context) + logging.info("%s (from level %s to level %s)", + evolver, + evolver.from_level, + evolver.to_level) + #try: + evolver.run() + #except: + #context.rollback() + #raise + context.set_schema_level(evolver.to_level) + #context.commit() + logging.info("%s is now at level %s",context,context.get_schema_level()) + + #if dryrun: # implement me with backup and restore + #logging.info("Rolling back changes on %s",context) + #context.rollback() + #else: + #logging.info("Committing changes on %s",context) + #context.commit() + + logging.info("Migration finished") + + +class MigrationStep: + """Base MigrationStep class, aka evolver. + + You develop your own steps, and then pass a list of those steps to the + Migrator instance that will run them in order. + + When the migrator runs, it will take the list of steps you gave him, + and, for each step: + + a) instantiate it, passing the context you gave to the migrator + into the step's __init__(). + b) run() the method in the migration step. + + As you can see, the default MigrationStep constructor makes the passed + context available as self.context in the methods of your step. + + Each step has two member vars that determine in which order they + are run, and if they need to run: + + - from_level = the schema level that the database should be at, + before running the evolver + The value None has special meaning here, it + means the first evolver that should be run if the + database does not have a schema level yet. + - to_level = the schema level number that the database will be at + after the evolver has run + """ + + # Implement these attributes in your steps + from_level = None + to_level = None + + def __init__(self,context): + self.context = context + + def run(self): + raise NotImplementedError + + +class MigrationContext: + def __init__(self): pass + def commit(self):raise NotImplementedError + def rollback(self):raise NotImplementedError + def get_schema_level(self):raise NotImplementedError + def set_schema_level(self,l):raise NotImplementedError + + diff --git a/cosmic-core/python/lib/cloudutils/__init__.py b/cosmic-core/python/lib/cloudutils/__init__.py new file mode 100644 index 0000000000..978b68af62 --- /dev/null +++ b/cosmic-core/python/lib/cloudutils/__init__.py @@ -0,0 +1,16 @@ +# 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. diff --git a/cosmic-core/python/lib/cloudutils/cloudException.py b/cosmic-core/python/lib/cloudutils/cloudException.py new file mode 100644 index 0000000000..9da29c23e0 --- /dev/null +++ b/cosmic-core/python/lib/cloudutils/cloudException.py @@ -0,0 +1,45 @@ +# 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. +import sys +import traceback +class CloudRuntimeException(Exception): + def __init__(self, errMsg): + self.errMsg = errMsg + + value = sys.exc_info()[1] + if value is not None: + self.errMsg += ", due to:" + str(value) + + self.details = formatExceptionInfo() + def __str__(self): + return self.errMsg + def getDetails(self): + return self.details + +class CloudInternalException(Exception): + def __init__(self, errMsg): + self.errMsg = errMsg + def __str__(self): + return self.errMsg + +def formatExceptionInfo(maxTBlevel=5): + cla, exc, trbk = sys.exc_info() + excTb = traceback.format_tb(trbk, maxTBlevel) + msg = str(exc) + "\n" + for tb in excTb: + msg += tb + return msg diff --git a/cosmic-core/python/lib/cloudutils/configFileOps.py b/cosmic-core/python/lib/cloudutils/configFileOps.py new file mode 100644 index 0000000000..e93182464d --- /dev/null +++ b/cosmic-core/python/lib/cloudutils/configFileOps.py @@ -0,0 +1,177 @@ +# 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. +import re +import tempfile +import shutil +from utilities import bash +class configFileOps: + class entry: + def __init__(self, name, value, op, separator): + self.name = name + self.value = value + self.state = "new" + self.op = op + self.separator = separator + def setState(self, state): + self.state = state + def getState(self): + return self.state + + def __init__(self, fileName, cfg=None): + self.fileName = fileName + self.entries = [] + self.backups = [] + + if cfg is not None: + cfg.cfoHandlers.append(self) + + def addEntry(self, name, value, separator="="): + e = self.entry(name, value, "add", separator) + self.entries.append(e) + + def rmEntry(self, name, value, separator="="): + entry = self.entry(name, value, "rm", separator) + self.entries.append(entry) + + def getEntry(self, name, separator="="): + try: + ctx = file(self.fileName).read(-1) + match = re.search("^" + name + ".*", ctx, re.MULTILINE) + if match is None: + return "" + line = match.group(0).split(separator, 1) + return line[1] + except: + return "" + + def save(self): + fp = open(self.fileName, "r") + newLines = [] + for line in fp.readlines(): + matched = False + for entry in self.entries: + if entry.op == "add": + if entry.separator == "=": + matchString = "^\ *" + entry.name + ".*" + elif entry.separator == " ": + matchString = "^\ *" + entry.name + "\ *" + entry.value + else: + if entry.separator == "=": + matchString = "^\ *" + entry.name + "\ *=\ *" + entry.value + else: + matchString = "^\ *" + entry.name + "\ *" + entry.value + + match = re.match(matchString, line) + if match is not None: + if entry.op == "add" and entry.separator == "=": + newline = "\n" + entry.name + "=" + entry.value + "\n" + entry.setState("set") + newLines.append(newline) + self.backups.append([line, newline]) + matched = True + break + elif entry.op == "rm": + entry.setState("set") + self.backups.append([line, None]) + matched = True + break + + if not matched: + newLines.append(line) + + for entry in self.entries: + if entry.getState() != "set": + if entry.op == "add": + newline = entry.name + entry.separator + entry.value + "\n" + newLines.append(newline) + self.backups.append([None, newline]) + entry.setState("set") + + fp.close() + + file(self.fileName, "w").writelines(newLines) + + def replace_line(self, startswith,stanza,always_add=False): + lines = [ s.strip() for s in file(self.fileName).readlines() ] + newlines = [] + replaced = False + for line in lines: + if re.search(startswith, line): + if stanza is not None: + newlines.append(stanza) + self.backups.append([line, stanza]) + replaced = True + else: newlines.append(line) + if not replaced and always_add: + newlines.append(stanza) + self.backups.append([None, stanza]) + newlines = [ s + '\n' for s in newlines ] + file(self.fileName,"w").writelines(newlines) + + def replace_or_add_line(self, startswith,stanza): + return self.replace_line(startswith,stanza,always_add=True) + + def add_lines(self, lines, addToBackup=True): + fp = file(self.fileName).read(-1) + sh = re.escape(lines) + match = re.search(sh, fp, re.MULTILINE) + if match is not None: + return + + fp += lines + file(self.fileName, "w").write(fp) + self.backups.append([None, lines]) + + def replace_lines(self, src, dst, addToBackup=True): + fp = file(self.fileName).read(-1) + sh = re.escape(src) + if dst is None: + dst = "" + repl,nums = re.subn(sh, dst, fp) + if nums <=0: + return + file(self.fileName, "w").write(repl) + if addToBackup: + self.backups.append([src, dst]) + + def append_lines(self, match_lines, append_lines): + fp = file(self.fileName).read(-1) + sh = re.escape(match_lines) + match = re.search(sh, fp, re.MULTILINE) + if match is None: + return + + sh = re.escape(append_lines) + if re.search(sh, fp, re.MULTILINE) is not None: + return + + newlines = [] + for line in file(self.fileName).readlines(): + if re.search(match_lines, line) is not None: + newlines.append(line + append_lines) + self.backups.append([line, line + append_lines]) + else: + newlines.append(line) + + file(self.fileName, "w").writelines(newlines) + + def backup(self): + for oldLine, newLine in self.backups: + if newLine is None: + self.add_lines(oldLine, False) + else: + self.replace_lines(newLine, oldLine, False) diff --git a/cosmic-core/python/lib/cloudutils/db.py b/cosmic-core/python/lib/cloudutils/db.py new file mode 100644 index 0000000000..8fe3195599 --- /dev/null +++ b/cosmic-core/python/lib/cloudutils/db.py @@ -0,0 +1,82 @@ +# 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. +import MySQLdb +import os +from utilities import bash +from cloudException import CloudRuntimeException +import sys +class Database: + """Database connection""" + def __init__(self, username, password=None, host='localhost', port='3306', db="cloud"): + self.host = host + self.username = username + self.password = password + self.port = port + self.db = db + + def execute(self, statement): + txn = None + try: + if self.password is not None: + txn = MySQLdb.Connect(host=self.host, user=self.username, + passwd=self.password, db=self.db) + else: + txn = MySQLdb.Connect(host=self.host, user=self.username, + db=self.db) + cursor = txn.cursor() + cursor.execute(statement) + cursor.close() + txn.commit() + if txn is not None: + try: + txn.close() + except: + pass + except: + if txn is not None: + try: + txn.close() + except: + pass + raise CloudRuntimeException("Failed to execute:%s"%statement) + + def testConnection(self): + try: + if self.password is not None: + db = MySQLdb.Connect(host=self.host, user=self.username, + passwd=self.password, db=self.db) + else: + db = MySQLdb.Connect(host=self.host, user=self.username, + db=self.db) + return True + except: + raise CloudRuntimeException("Failed to Connect to DB") + + def executeFromFile(self, file): + if not os.path.exists(file): + return False + + cmdLine = "mysql --host=" + self.host + " --port=" + str(self.port) + " --user=" + self.username + if self.password is not None: + cmdLine += " --password=" + self.password + + cmdLine += " < " + file + + try: + bash(cmdLine) + except: + raise CloudRuntimeException("Failed to execute " + cmdLine) diff --git a/cosmic-core/python/lib/cloudutils/globalEnv.py b/cosmic-core/python/lib/cloudutils/globalEnv.py new file mode 100644 index 0000000000..e0a61df623 --- /dev/null +++ b/cosmic-core/python/lib/cloudutils/globalEnv.py @@ -0,0 +1,50 @@ +# 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. +class globalEnv: + def __init__(self): + #Agent/Server/Db + self.mode = None + #server mode: normal/mycloud + self.svrMode = None + #noStart: do not start mgmt server after configuration? + self.noStart = False + #myCloud/Agent/Console + self.agentMode = None + #Tomcat6/Tomcat7 + self.svrConf = None + #debug + self.debug = False + #management server IP + self.mgtSvr = "myagent.cloud.com" + #zone id or zone name + self.zone = None + #pod id or pod name + self.pod = None + #cluster id or cluster name + self.cluster = None + #hypervisor type. KVM. Default is KVM + self.hypervisor = "kvm" + #nics: 0: private nic, 1: guest nic, 2: public nic used by agent + self.nics = [] + #uuid + self.uuid = None + #default private network + self.privateNet = "cloudbr0" + #distribution + self.distribution = None + # bridgeType + self.bridgeType = "native" diff --git a/cosmic-core/python/lib/cloudutils/networkConfig.py b/cosmic-core/python/lib/cloudutils/networkConfig.py new file mode 100644 index 0000000000..41ef9d93ed --- /dev/null +++ b/cosmic-core/python/lib/cloudutils/networkConfig.py @@ -0,0 +1,164 @@ +# 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. +from utilities import bash +from cloudException import CloudRuntimeException, CloudInternalException +import logging +import os +import re +import subprocess + +class networkConfig: + class devInfo: + def __init__(self, macAddr, ipAddr, netmask, gateway, type, name): + self.name = name + self.macAdrr = macAddr + self.ipAddr = ipAddr + self.netmask = netmask + self.gateway = gateway + self.type = type + self.name = name + #dhcp or static + self.method = None + + @staticmethod + def listNetworks(): + devs = os.listdir("/sys/class/net/") + devs = filter(networkConfig.isBridge, devs) + return devs + @staticmethod + def getDefaultNetwork(): + cmd = bash("route -n|awk \'/^0.0.0.0/ {print $2,$8}\'") + if not cmd.isSuccess(): + logging.debug("Failed to get default route") + raise CloudRuntimeException("Failed to get default route") + + result = cmd.getStdout().split(" ") + gateway = result[0] + dev = result[1] + + pdi = networkConfig.getDevInfo(dev) + logging.debug("Found default network device:%s"%pdi.name) + pdi.gateway = gateway + return pdi + + @staticmethod + def createBridge(dev, brName): + if not networkConfig.isBridgeSupported(): + logging.debug("bridge is not supported") + return False + if networkConfig.isBridgeEnslavedWithDevices(brName): + logging.debug("bridge: %s has devices enslaved"%brName) + return False + + cmds = "" + if not networkConfig.isBridge(brName): + cmds = "brctl addbr %s ;"%brName + + cmds += "ifconfig %s up;"%brName + cmds += "brctl addif %s %s"%(brName, dev) + return bash(cmds).isSuccess() + + @staticmethod + def isBridgeEnslavedWithDevices(brName): + if not networkConfig.isBridge(brName): + return False + + if not os.listdir("/sys/class/net/%s/brif"%brName): + return False + + return True + + @staticmethod + def isBridgeSupported(): + if os.path.exists("/proc/sys/net/bridge"): + return True + + return bash("modprobe -b bridge").isSucess() + + @staticmethod + def isNetworkDev(devName): + return os.path.exists("/sys/class/net/%s" % devName) + + @staticmethod + def isBridgePort(devName): + return os.path.exists("/sys/class/net/%s/brport" % devName) + + @staticmethod + def isBridge(devName): + return os.path.exists("/sys/class/net/%s/bridge" % devName) + + @staticmethod + def isOvsBridge(devName): + try: + return 0==subprocess.check_call(("ovs-vsctl", "br-exists", devName)) + except subprocess.CalledProcessError: + return False + + @staticmethod + def getBridge(devName): + bridgeName = None + if os.path.exists("/sys/class/net/%s/brport/bridge"%devName): + realPath = os.path.realpath("/sys/class/net/%s/brport/bridge"%devName) + bridgeName = realPath.split("/")[-1] + return bridgeName + + @staticmethod + def getEnslavedDev(br, brPort): + if not networkConfig.isBridgeEnslavedWithDevices(br): + return None + + for dev in os.listdir("/sys/class/net/%s/brif"%br): + br_port = int(file("/sys/class/net/%s/brif/%s/port_no"%(br,dev)).readline().strip("\n"), 16) + if br_port == brPort: + return dev + + return None + + @staticmethod + def getDevInfo(dev): + if not networkConfig.isNetworkDev(dev): + logging.debug("dev: " + dev + " is not a network device") + raise CloudInternalException("dev: " + dev + " is not a network device") + + netmask = None + ipAddr = None + macAddr = None + + cmd = bash("ifconfig " + dev) + if not cmd.isSuccess(): + logging.debug("Failed to get address from ifconfig") + raise CloudInternalException("Failed to get network info by ifconfig %s"%dev) + + for line in cmd.getLines(): + if line.find("HWaddr") != -1: + macAddr = line.split("HWaddr ")[1].strip(" ") + elif line.find("inet ") != -1: + m = re.search("addr:(.*)\ *Bcast:(.*)\ *Mask:(.*)", line) + if m is not None: + ipAddr = m.group(1).rstrip(" ") + netmask = m.group(3).rstrip(" ") + + if networkConfig.isBridgePort(dev): + type = "brport" + elif networkConfig.isBridge(dev): + type = "bridge" + else: + type = "dev" + + return networkConfig.devInfo(macAddr, ipAddr, netmask, None, type, dev) + + diff --git a/cosmic-core/python/lib/cloudutils/serviceConfig.py b/cosmic-core/python/lib/cloudutils/serviceConfig.py new file mode 100755 index 0000000000..29286900b1 --- /dev/null +++ b/cosmic-core/python/lib/cloudutils/serviceConfig.py @@ -0,0 +1,742 @@ +# 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. +from utilities import writeProgressBar, bash +from cloudException import CloudRuntimeException, CloudInternalException, formatExceptionInfo +import logging +from networkConfig import networkConfig +import re +from configFileOps import configFileOps +import os +import shutil + +class serviceCfgBase(object): + def __init__(self, syscfg): + self.status = None + self.serviceName = "" + self.cfoHandlers = [] + self.syscfg = syscfg + self.netMgrRunning = False + + def configration(self): + writeProgressBar("Configure " + self.serviceName + " ...", None) + result = False + try: + result = self.config() + if result is None: + result = False + + self.status = result + writeProgressBar(None, result) + return result + except CloudRuntimeException, e: + self.status = result + writeProgressBar(None, result) + logging.debug(e.getDetails()) + raise e + except CloudInternalException, e: + self.status = result + writeProgressBar(None, result) + raise e + except: + logging.debug(formatExceptionInfo()) + if self.syscfg.env.mode == "Server": + raise CloudRuntimeException("Configure %s failed, Please check the /var/log/cosmic/management/setupManagement.log for detail"%self.serviceName) + else: + raise CloudRuntimeException("Configure %s failed, Please check the /var/log/cosmic/agent/setup.log for detail"%self.serviceName) + + def backup(self): + if self.status is None: + return True + + writeProgressBar("Restore " + self.serviceName + " ...", None) + result = False + try: + for cfo in self.cfoHandlers: + cfo.backup() + + result = self.restore() + except (CloudRuntimeException, CloudInternalException), e: + logging.debug(e) + + writeProgressBar(None, result) + + def config(self): + return True + + def restore(self): + return True + +class networkConfigBase: + def __init__(self, syscfg): + self.netcfg = networkConfig() + self.serviceName = "Network" + self.brName = None + self.dev = None + self.syscfg = syscfg + + def isPreConfiged(self): + preCfged = False + for br in self.syscfg.env.nics: + if not self.netcfg.isNetworkDev(br): + logging.debug("%s is not a network device, is it down?"%br) + return False + if self.syscfg.env.bridgeType == "openvswitch" and not self.netcfg.isOvsBridge(br): + raise CloudInternalException("%s is not an openvswitch bridge" % br) + if self.syscfg.env.bridgeType == "native" and not self.netcfg.isBridge(br) and not self.netcfg.isNetworkDev(br): + # traffic label doesn't have to be a bridge, we'll create bridges on it + raise CloudInternalException("%s is not a bridge and not a net device" % br) + preCfged = True + + return preCfged + + def cfgNetwork(self, dev=None, brName=None): + if dev is None: + device = self.netcfg.getDefaultNetwork() + else: + device = self.netcfg.getDevInfo(dev) + + if device.type == "dev": + if brName is None: + brName = "cloudbr0" + + self.writeToCfgFile(brName, device) + elif device.type == "brport": + brName = self.netcfg.getBridge(dev) + brDevice = self.netcfg.getDevInfo(brName) + self.writeToCfgFile(brDevice.name, device) + elif device.type == "bridge": + #Fixme, assuming the outgoing physcial device is on port 1 + enslavedDev = self.netcfg.getEnslavedDev(device.name, 1) + if enslavedDev is None: + raise CloudInternalException("Failed to get enslaved devices on bridge:%s"%device.name) + + brDevice = device + device = self.netcfg.getDevInfo(enslavedDev) + brName = brDevice.name + self.writeToCfgFile(brName, device) + + self.brName = brName + self.dev = device.name + + def writeToCfgFile(self): + pass + +class networkConfigUbuntu(serviceCfgBase, networkConfigBase): + def __init__(self, syscfg): + super(networkConfigUbuntu, self).__init__(syscfg) + networkConfigBase.__init__(self, syscfg) + self.netCfgFile = "/etc/network/interfaces" + + def getNetworkMethod(self, line): + if line.find("static") != -1: + return "static" + elif line.find("dhcp") != -1: + return "dhcp" + else: + logging.debug("Failed to find the network method from:%s"%line) + raise CloudInternalException("Failed to find the network method from /etc/network/interfaces") + + def addBridge(self, br, dev): + bash("ifdown %s"%dev.name) + for line in file(self.netCfgFile).readlines(): + match = re.match("^ *iface %s.*"%dev.name, line) + if match is not None: + dev.method = self.getNetworkMethod(match.group(0)) + cfo = configFileOps(self.netCfgFile, self) + if self.syscfg.env.bridgeType == "openvswitch": + bridgeCfg = "\n".join(("", + "iface {device} inet manual", + " ovs_type OVSPort", + " ovs_bridge {bridge}", + "", + "auto {bridge}", + "allow-ovs {bridge}", + "iface {bridge} inet {device_method}", + " ovs_type OVSBridge", + " ovs_ports {device}", + "")).format(bridge=br, device=dev.name, device_method=dev.method) + cfo.replace_line("^ *auto %s.*" % dev.name, + "allow-{bridge} {device}".format(bridge=br, device=dev.name)) + elif self.syscfg.env.bridgeType == "native": + bridgeCfg = "\niface %s inet manual\n \ + auto %s\n \ + iface %s inet %s\n \ + bridge_ports %s\n"%(dev.name, br, br, dev.method, dev.name) + else: + raise CloudInternalException("Unknown network.bridge.type %s" % self.syscfg.env.bridgeType) + cfo.replace_line("^ *iface %s.*"%dev.name, bridgeCfg) + + def addDev(self, br, dev): + logging.debug("Haven't implement yet") + + def addBridgeAndDev(self, br, dev): + logging.debug("Haven't implement yet") + + def writeToCfgFile(self, br, dev): + cfg = file(self.netCfgFile).read() + ifaceDev = re.search("^ *iface %s.*"%dev.name, cfg, re.MULTILINE) + ifaceBr = re.search("^ *iface %s.*"%br, cfg, re.MULTILINE) + if ifaceDev is not None and ifaceBr is not None: + logging.debug("%s:%s already configured"%(br, dev.name)) + return True + elif ifaceDev is not None and ifaceBr is None: + #reconfig bridge + self.addBridge(br, dev) + elif ifaceDev is None and ifaceBr is not None: + #reconfig dev + raise CloudInternalException("Missing device configuration, Need to add your network configuration into /etc/network/interfaces at first") + else: + raise CloudInternalException("Missing bridge/device network configuration, need to add your network configuration into /etc/network/interfaces at first") + + def config(self): + try: + if super(networkConfigUbuntu, self).isPreConfiged(): + return True + + self.netMgrRunning = self.syscfg.svo.isServiceRunning("network-manager") + super(networkConfigUbuntu, self).cfgNetwork() + if self.netMgrRunning: + self.syscfg.svo.stopService("network-manager") + self.syscfg.svo.disableService("network-manager") + + ifup_op = bash("ifup %s"%self.brName) + if not ifup_op.isSuccess(): + raise CloudInternalException("Can't start network:%s %s" % (self.brName, ifup_op.getErrMsg())) + + self.syscfg.env.nics.append(self.brName) + self.syscfg.env.nics.append(self.brName) + self.syscfg.env.nics.append(self.brName) + return True + except: + raise + + def restore(self): + try: + if self.netMgrRunning: + self.syscfg.svo.enableService("network-manager") + self.syscfg.svo.startService("network-manager") + + bash("/etc/init.d/networking stop") + bash("/etc/init.d/networking start") + return True + except: + logging.debug(formatExceptionInfo()) + return False + +class networkConfigRedhat(serviceCfgBase, networkConfigBase): + def __init__(self, syscfg): + super(networkConfigRedhat, self).__init__(syscfg) + networkConfigBase.__init__(self, syscfg) + + def writeToCfgFile(self, brName, dev): + self.devCfgFile = "/etc/sysconfig/network-scripts/ifcfg-%s" % dev.name + self.brCfgFile = "/etc/sysconfig/network-scripts/ifcfg-%s" % brName + + isDevExist = os.path.exists(self.devCfgFile) + isBrExist = os.path.exists(self.brCfgFile) + if isDevExist and isBrExist: + logging.debug("%s:%s already configured"%(brName, dev.name)) + return True + elif isDevExist and not isBrExist: + #reconfig bridge + self.addBridge(brName, dev) + elif not isDevExist and isBrExist: + #reconfig dev + raise CloudInternalException("Missing device configuration, Need to add your network configuration into /etc/sysconfig/network-scripts at first") + else: + raise CloudInternalException("Missing bridge/device network configuration, need to add your network configuration into /etc/sysconfig/network-scripts at first") + + + def addBridge(self, brName, dev): + bash("ifdown %s" % dev.name) + + if not os.path.exists(self.brCfgFile): + shutil.copy(self.devCfgFile, self.brCfgFile) + + #config device file at first: disable nm, set onboot=yes if not + cfo = configFileOps(self.devCfgFile, self) + cfo.addEntry("NM_CONTROLLED", "no") + cfo.addEntry("ONBOOT", "yes") + if self.syscfg.env.bridgeType == "openvswitch": + if cfo.getEntry("IPADDR"): + cfo.rmEntry("IPADDR", cfo.getEntry("IPADDR")) + cfo.addEntry("DEVICETYPE", "ovs") + cfo.addEntry("TYPE", "OVSPort") + cfo.addEntry("OVS_BRIDGE", brName) + elif self.syscfg.env.bridgeType == "native": + cfo.addEntry("BRIDGE", brName) + else: + raise CloudInternalException("Unknown network.bridge.type %s" % self.syscfg.env.bridgeType) + cfo.save() + + cfo = configFileOps(self.brCfgFile, self) + cfo.addEntry("NM_CONTROLLED", "no") + cfo.addEntry("ONBOOT", "yes") + cfo.addEntry("DEVICE", brName) + if self.syscfg.env.bridgeType == "openvswitch": + if cfo.getEntry("HWADDR"): + cfo.rmEntry("HWADDR", cfo.getEntry("HWADDR")) + if cfo.getEntry("UUID"): + cfo.rmEntry("UUID", cfo.getEntry("UUID")) + cfo.addEntry("STP", "yes") + cfo.addEntry("DEVICETYPE", "ovs") + cfo.addEntry("TYPE", "OVSBridge") + elif self.syscfg.env.bridgeType == "native": + cfo.addEntry("TYPE", "Bridge") + else: + raise CloudInternalException("Unknown network.bridge.type %s" % self.syscfg.env.bridgeType) + cfo.save() + + def config(self): + try: + if super(networkConfigRedhat, self).isPreConfiged(): + return True + + super(networkConfigRedhat, self).cfgNetwork() + + self.netMgrRunning = self.syscfg.svo.isServiceRunning("NetworkManager") + if self.netMgrRunning: + self.syscfg.svo.stopService("NetworkManager") + self.syscfg.svo.disableService("NetworkManager") + + cfo = configFileOps("/etc/sysconfig/network", self) + cfo.addEntry("NOZEROCONF", "yes") + cfo.save() + + if not bash("service network restart").isSuccess(): + raise CloudInternalException("Can't restart network") + + self.syscfg.env.nics.append(self.brName) + self.syscfg.env.nics.append(self.brName) + self.syscfg.env.nics.append(self.brName) + return True + except: + raise + + def restore(self): + try: + if self.netMgrRunning: + self.syscfg.svo.enableService("NetworkManager") + self.syscfg.svo.startService("NetworkManager") + bash("service network restart") + return True + except: + logging.debug(formatExceptionInfo()) + return False + +class cgroupConfig(serviceCfgBase): + def __init__(self, syscfg): + super(cgroupConfig, self).__init__(syscfg) + self.serviceName = "Cgroup" + + def config(self): + try: + cfo = configFileOps("/etc/cgconfig.conf", self) + addConfig = "group virt {\n \ + cpu {\n \ + cpu.shares = 9216;\n \ + }\n \ + }\n" + cfo.add_lines(addConfig) + + self.syscfg.svo.stopService("cgconfig", True) + self.syscfg.svo.enableService("cgconfig",forcestart=True) + + cfo = configFileOps("/etc/cgrules.conf", self) + cfgline = "root:/usr/sbin/libvirtd cpu virt/\n" + cfo.add_lines(cfgline) + + self.syscfg.svo.stopService("cgred", True) + if not self.syscfg.svo.enableService("cgred"): + return False + return True + except: + raise + + def restore(self): + try: + self.syscfg.svo.stopService("cgconfig") + self.syscfg.svo.enableService("cgconfig",forcestart=True) + self.syscfg.svo.stopService("cgred") + self.syscfg.svo.enableService("cgred") + return True + except: + logging.debug(formatExceptionInfo()) + return False + +class nfsConfig(serviceCfgBase): + def __init__(self, syscfg): + super(nfsConfig, self).__init__(syscfg) + self.serviceName = "Nfs" + + def config(self): + try: + if not os.path.exists("/etc/nfsmount.conf"): + return True + + cfo = configFileOps("/etc/nfsmount.conf") + cfo.addEntry("Ac", "False") + cfo.addEntry("actimeo", "0") + cfo.save() + + self.syscfg.svo.enableService("rpcbind") + self.syscfg.svo.stopService("rpcbind") + self.syscfg.svo.startService("rpcbind") + + self.syscfg.svo.enableService("nfs") + self.syscfg.svo.stopService("nfs") + self.syscfg.svo.startService("nfs") + + return True + except: + logging.debug(formatExceptionInfo()) + return False + +class securityPolicyConfigUbuntu(serviceCfgBase): + def __init__(self, syscfg): + super(securityPolicyConfigUbuntu, self).__init__(syscfg) + self.serviceName = "Apparmor" + + def config(self): + try: + cmd = bash("service apparmor status") + if not cmd.isSuccess() or cmd.getStdout() == "": + self.spRunning = False + return True + + if not bash("apparmor_status |grep libvirt").isSuccess(): + return True + + bash("ln -s /etc/apparmor.d/usr.sbin.libvirtd /etc/apparmor.d/disable/") + bash("ln -s /etc/apparmor.d/usr.lib.libvirt.virt-aa-helper /etc/apparmor.d/disable/") + bash("apparmor_parser -R /etc/apparmor.d/usr.sbin.libvirtd") + bash("apparmor_parser -R /etc/apparmor.d/usr.lib.libvirt.virt-aa-helper") + + return True + except: + raise CloudRuntimeException("Failed to configure apparmor, please see the /var/log/cosmic/agent/setup.log for detail, \ + or you can manually disable it before starting myCloud") + + def restore(self): + try: + self.syscfg.svo.enableService("apparmor") + self.syscfg.svo.startService("apparmor") + return True + except: + logging.debug(formatExceptionInfo()) + return False + +class securityPolicyConfigRedhat(serviceCfgBase): + def __init__(self, syscfg): + super(securityPolicyConfigRedhat, self).__init__(syscfg) + self.serviceName = "SElinux" + + def config(self): + selinuxEnabled = True + + if not bash("selinuxenabled").isSuccess(): + selinuxEnabled = False + + if selinuxEnabled: + try: + bash("setenforce 0") + cfo = configFileOps("/etc/selinux/config", self) + cfo.replace_line("SELINUX=", "SELINUX=permissive") + return True + except: + raise CloudRuntimeException("Failed to configure selinux, please see the /var/log/cosmic/agent/setup.log for detail, \ + or you can manually disable it before starting myCloud") + else: + return True + + def restore(self): + try: + bash("setenforce 1") + return True + except: + logging.debug(formatExceptionInfo()) + return False + +class libvirtConfigRedhat(serviceCfgBase): + def __init__(self, syscfg): + super(libvirtConfigRedhat, self).__init__(syscfg) + self.serviceName = "Libvirt" + + def config(self): + try: + cfo = configFileOps("/etc/libvirt/libvirtd.conf", self) + cfo.addEntry("listen_tcp", "1") + cfo.addEntry("tcp_port", "\"16509\"") + cfo.addEntry("auth_tcp", "\"none\"") + cfo.addEntry("listen_tls", "0") + cfo.save() + + cfo = configFileOps("/etc/sysconfig/libvirtd", self) + cfo.addEntry("export CGROUP_DAEMON", "'cpu:/virt'") + cfo.addEntry("LIBVIRTD_ARGS", "-l") + cfo.save() + + filename = "/etc/libvirt/qemu.conf" + + cfo = configFileOps(filename, self) + cfo.addEntry("security_driver", "\"none\"") + cfo.addEntry("user", "\"root\"") + cfo.addEntry("group", "\"root\"") + cfo.addEntry("vnc_listen", "\"0.0.0.0\"") + cfo.save() + + self.syscfg.svo.stopService("libvirtd") + if not self.syscfg.svo.startService("libvirtd"): + return False + + return True + except: + raise + + def restore(self): + pass + +class libvirtConfigUbuntu(serviceCfgBase): + def __init__(self, syscfg): + super(libvirtConfigUbuntu, self).__init__(syscfg) + self.serviceName = "Libvirt" + + def setupLiveMigration(self): + cfo = configFileOps("/etc/libvirt/libvirtd.conf", self) + cfo.addEntry("listen_tcp", "1") + cfo.addEntry("tcp_port", "\"16509\""); + cfo.addEntry("auth_tcp", "\"none\""); + cfo.addEntry("listen_tls", "0") + cfo.save() + + if os.path.exists("/etc/init/libvirt-bin.conf"): + cfo = configFileOps("/etc/init/libvirt-bin.conf", self) + cfo.replace_line("exec /usr/sbin/libvirtd","exec /usr/sbin/libvirtd -d -l") + else: + cfo = configFileOps("/etc/default/libvirt-bin", self) + cfo.replace_or_add_line("libvirtd_opts=","libvirtd_opts='-l -d'") + + def config(self): + try: + self.setupLiveMigration() + + filename = "/etc/libvirt/qemu.conf" + + cfo = configFileOps(filename, self) + cfo.addEntry("security_driver", "\"none\"") + cfo.addEntry("user", "\"root\"") + cfo.addEntry("group", "\"root\"") + cfo.save() + + self.syscfg.svo.stopService("libvirt-bin") + self.syscfg.svo.enableService("libvirt-bin") + return True + except: + raise + + def restore(self): + try: + self.syscfg.svo.stopService("libvirt-bin") + self.syscfg.svo.startService("libvirt-bin") + return True + except: + logging.debug(formatExceptionInfo()) + return False + +class firewallConfigUbuntu(serviceCfgBase): + def __init__(self, syscfg): + super(firewallConfigUbuntu, self).__init__(syscfg) + self.serviceName = "Firewall" + + def config(self): + try: + ports = "22 1798 16509".split() + for p in ports: + bash("ufw allow %s"%p) + bash("ufw allow proto tcp from any to any port 5900:6100") + bash("ufw allow proto tcp from any to any port 49152:49216") + self.syscfg.svo.stopService("ufw") + self.syscfg.svo.startService("ufw") + return True + except: + raise + + def restore(self): + return True + +class firewallConfigBase(serviceCfgBase): + def __init__(self, syscfg): + super(firewallConfigBase, self).__init__(syscfg) + self.serviceName = "Firewall" + self.rules = [] + + def allowPort(self, port): + status = False + try: + status = bash("iptables-save|grep INPUT|grep -w %s"%port).isSuccess() + except: + pass + + if not status: + redo = False + result = True + try: + result = bash("iptables -I INPUT -p tcp -m tcp --dport %s -j ACCEPT"%port).isSuccess() + except: + redo = True + + if not result or redo: + bash("sleep 30") + bash("iptables -I INPUT -p tcp -m tcp --dport %s -j ACCEPT"%port) + + def config(self): + try: + for port in self.ports: + self.allowPort(port) + + for rule in self.rules: + bash("iptables " + rule) + + bash("iptables-save > /etc/sysconfig/iptables") + self.syscfg.svo.stopService("iptables") + self.syscfg.svo.startService("iptables") + return True + except: + raise + + def restore(self): + return True + +class firewallConfigAgent(firewallConfigBase): + def __init__(self, syscfg): + super(firewallConfigAgent, self).__init__(syscfg) + self.ports = "22 16509 5900:6100 49152:49216".split() + if syscfg.env.distribution.getVersion() == "CentOS": + self.rules = ["-D FORWARD -j RH-Firewall-1-INPUT"] + else: + self.rules = ["-D FORWARD -j REJECT --reject-with icmp-host-prohibited"] + + +class cloudAgentConfig(serviceCfgBase): + def __init__(self, syscfg): + super(cloudAgentConfig, self).__init__(syscfg) + if syscfg.env.agentMode == "Agent": + self.serviceName = "cloudAgent" + elif syscfg.env.agentMode == "myCloud": + self.serviceName = "myCloud" + elif syscfg.env.agentMode == "Console": + self.serviceName = "Console Proxy" + + def configMyCloud(self): + try: + cfo = configFileOps("/etc/cosmic/agent/agent.properties", self) + cfo.addEntry("host", self.syscfg.env.mgtSvr) + cfo.addEntry("zone", self.syscfg.env.zone) + cfo.addEntry("port", "443") + if cfo.getEntry("local.storage.uuid") == "": + cfo.addEntry("local.storage.uuid", str(bash("uuidgen").getStdout())) + cfo.addEntry("guid", str(self.syscfg.env.uuid)) + cfo.addEntry("mount.path", "/mnt") + cfo.addEntry("resource", "com.cloud.storage.resource.LocalSecondaryStorageResource|com.cloud.agent.resource.computing.CloudZonesComputingResource") + cfo.save() + + #self.syscfg.svo.stopService("cloud-agent") + #self.syscfg.svo.enableService("cloud-agent") + return True + except: + raise + + def configAgent(self): + try: + cfo = configFileOps("/etc/cosmic/agent/agent.properties", self) + cfo.addEntry("host", self.syscfg.env.mgtSvr) + cfo.addEntry("zone", self.syscfg.env.zone) + cfo.addEntry("pod", self.syscfg.env.pod) + cfo.addEntry("cluster", self.syscfg.env.cluster) + cfo.addEntry("hypervisor.type", self.syscfg.env.hypervisor) + cfo.addEntry("port", "8250") + cfo.addEntry("guid", str(self.syscfg.env.uuid)) + if cfo.getEntry("local.storage.uuid") == "": + cfo.addEntry("local.storage.uuid", str(bash("uuidgen").getStdout())) + if cfo.getEntry("resource") == "": + cfo.addEntry("resource", "com.cloud.hypervisor.kvm.resource.LibvirtComputingResource") + cfo.save() + + self.syscfg.svo.stopService("cosmic-agent") + bash("sleep 30") + self.syscfg.svo.enableService("cosmic-agent") + return True + except: + raise + + def configConsole(self): + try: + cfo = configFileOps("/etc/cosmic/agent/agent.properties", self) + cfo.addEntry("host", self.syscfg.env.mgtSvr) + cfo.addEntry("zone", self.syscfg.env.zone) + cfo.addEntry("pod", self.syscfg.env.pod) + cfo.addEntry("cluster", self.syscfg.env.cluster) + cfo.addEntry("port", "8250") + cfo.addEntry("guid", str(self.syscfg.env.uuid)) + cfo.addEntry("resource", "com.cloud.agent.resource.computing.consoleProxyResource") + cfo.save() + + self.syscfg.svo.stopService("cosmic-agent") + self.syscfg.svo.enableService("cosmic-agent") + return True + except: + raise + + def config(self): + if self.syscfg.env.agentMode == "Agent": + return self.configAgent() + elif self.syscfg.env.agentMode == "myCloud": + return self.configMyCloud() + elif self.syscfg.env.agentMode == "console": + return self.configConsole() + + def restore(self): + return True + +class firewallConfigServer(firewallConfigBase): + def __init__(self, syscfg): + super(firewallConfigServer, self).__init__(syscfg) + #9090 is used for cluster management server + if self.syscfg.env.svrMode == "myCloud": + self.ports = "443 8080 8250 8443 9090".split() + else: + self.ports = "8080 8250 9090".split() + +class ubuntuFirewallConfigServer(firewallConfigServer): + def allowPort(self, port): + status = False + try: + status = bash("iptables-save|grep INPUT|grep -w %s"%port).isSuccess() + except: + pass + + if not status: + bash("ufw allow %s/tcp"%port) + + def config(self): + try: + for port in self.ports: + self.allowPort(port) + + return True + except: + raise diff --git a/cosmic-core/python/lib/cloudutils/serviceConfigServer.py b/cosmic-core/python/lib/cloudutils/serviceConfigServer.py new file mode 100644 index 0000000000..7812fff4f7 --- /dev/null +++ b/cosmic-core/python/lib/cloudutils/serviceConfigServer.py @@ -0,0 +1,150 @@ +# 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. +from db import Database +from configFileOps import configFileOps +from serviceConfig import serviceCfgBase +from cloudException import CloudRuntimeException, CloudInternalException +from utilities import bash +import os + +class cloudManagementConfig(serviceCfgBase): + def __init__(self, syscfg): + super(cloudManagementConfig, self).__init__(syscfg) + self.serviceName = "CloudStack Management Server" + + def config(self): + def checkHostName(): + ret = bash("hostname --fqdn") + if not ret.isSuccess(): + raise CloudInternalException("Cannot get hostname, 'hostname --fqdn failed'") + + if self.syscfg.env.svrMode == "mycloud": + cfo = configFileOps("/usr/share/cloudstack-management/conf/environment.properties", self) + cfo.addEntry("cloud-stack-components-specification", "components-cloudzones.xml") + cfo.save() + + cfo = configFileOps("/usr/share/cloudstack-management/conf/db.properties", self) + dbHost = cfo.getEntry("db.cloud.host") + dbPort = cfo.getEntry("db.cloud.port") + dbUser = cfo.getEntry("db.cloud.username") + dbPass = cfo.getEntry("db.cloud.password") + if dbPass.strip() == "": + dbPass = None + dbName = cfo.getEntry("db.cloud.name") + db = Database(dbUser, dbPass, dbHost, dbPort, dbName) + + try: + db.testConnection() + except CloudRuntimeException, e: + raise e + except: + raise CloudInternalException("Failed to connect to Mysql server") + + try: + statement = """ UPDATE configuration SET value='%s' WHERE name='%s'""" + + db.execute(statement%('true','use.local.storage')) + db.execute(statement%('20','max.template.iso.size')) + + statement = """ UPDATE vm_template SET url='%s',checksum='%s' WHERE id='%s' """ + db.execute(statement%('https://rightscale-cloudstack.s3.amazonaws.com/kvm/RightImage_CentOS_5.4_x64_v5.6.28.qcow2.bz2', '90fcd2fa4d3177e31ff296cecb9933b7', '4')) + + statement="""UPDATE disk_offering set use_local_storage=1""" + db.execute(statement) + except: + raise e + + #add DNAT 443 to 8250 + if not bash("iptables-save |grep PREROUTING | grep 8250").isSuccess(): + bash("iptables -A PREROUTING -t nat -p tcp --dport 443 -j REDIRECT --to-port 8250 ") + + #generate keystore + keyPath = "/var/cloudstack/management/web.keystore" + if not os.path.exists(keyPath): + cmd = bash("keytool -genkey -keystore %s -storepass \"cloud.com\" -keypass \"cloud.com\" -validity 3650 -dname cn=\"Cloudstack User\",ou=\"mycloud.cloud.com\",o=\"mycloud.cloud.com\",c=\"Unknown\""%keyPath) + + if not cmd.isSuccess(): + raise CloudInternalException(cmd.getErrMsg()) + if not self.syscfg.env.svrConf == "Tomcat7": + cfo = configFileOps("/etc/cloudstack/management/tomcat6.conf", self) + cfo.add_lines("JAVA_OPTS+=\" -Djavax.net.ssl.trustStore=%s \""%keyPath) + elif self.syscfg.env.svrMode == "HttpsServer": + if self.syscfg.env.svrConf == "Tomcat7": + if not os.path.exists("/etc/cloudstack/management/server7-ssl.xml"): + raise CloudRuntimeException("Cannot find /etc/cloudstack/management/server7-ssl.xml, https enable failed") + if os.path.exists("/etc/cloudstack/management/server.xml"): + bash("rm -f /etc/cloudstack/management/server.xml") + bash("ln -s /etc/cloudstack/management/server7-ssl.xml /etc/cloudstack/management/server.xml") + else: + if not os.path.exists("/etc/cloudstack/management/server-ssl.xml") or not os.path.exists("/etc/cloudstack/management/tomcat6-ssl.conf"): + raise CloudRuntimeException("Cannot find /etc/cloudstack/management/server-ssl.xml or /etc/cloudstack/management/tomcat6-ssl.conf, https enable failed") + if os.path.exists("/etc/cloudstack/management/server.xml"): + bash("rm -f /etc/cloudstack/management/server.xml") + if os.path.exists("/etc/cloudstack/management/tomcat6.conf"): + bash("rm -f /etc/cloudstack/management/tomcat6.conf") + bash("ln -s /etc/cloudstack/management/server-ssl.xml /etc/cloudstack/management/server.xml") + bash("ln -s /etc/cloudstack/management/tomcat6-ssl.conf /etc/cloudstack/management/tomcat6.conf") + if not bash("iptables-save |grep PREROUTING | grep 6443").isSuccess(): + bash("iptables -A PREROUTING -t nat -p tcp --dport 443 -j REDIRECT --to-port 6443") + else: + if self.syscfg.env.svrConf == "Tomcat7": + if not os.path.exists("/etc/cloudstack/management/server7-nonssl.xml"): + raise CloudRuntimeException("Cannot find /etc/cloudstack/management/server7-nonssl.xml, https enable failed") + if os.path.exists("/etc/cloudstack/management/server.xml"): + bash("rm -f /etc/cloudstack/management/server.xml") + bash("ln -s /etc/cloudstack/management/server7-nonssl.xml /etc/cloudstack/management/server.xml") + else: + if not os.path.exists("/etc/cloudstack/management/server-nonssl.xml") or not os.path.exists("/etc/cloudstack/management/tomcat6-nonssl.conf"): + raise CloudRuntimeException("Cannot find /etc/cloudstack/management/server-nonssl.xml or /etc/cloudstack/management/tomcat6-nonssl.conf, https enable failed") + if os.path.exists("/etc/cloudstack/management/server.xml"): + bash("rm -f /etc/cloudstack/management/server.xml") + if os.path.exists("/etc/cloudstack/management/tomcat6.conf"): + bash("rm -f /etc/cloudstack/management/tomcat6.conf") + bash("ln -s /etc/cloudstack/management/server-nonssl.xml /etc/cloudstack/management/server.xml") + bash("ln -s /etc/cloudstack/management/tomcat6-nonssl.conf /etc/cloudstack/management/tomcat6.conf") + bash("touch /var/run/cloudstack-management.pid") + bash("chown cloud.cloud /var/run/cloudstack-management.pid") + #distro like sl 6.1 needs this folder, or tomcat6 failed to start + checkHostName() + bash("mkdir /var/log/cloudstack-management/") + bash("chown cloud:cloud -R /var/lib/cloudstack/") + bash("chmod +x -R /usr/share/cloudstack-management/webapps/client/WEB-INF/classes/scripts/") + #set max process per account is unlimited + if os.path.exists("/etc/security/limits.conf"): + cfo = configFileOps("/etc/security/limits.conf") + cfo.add_lines("cloud soft nproc -1\n") + cfo.add_lines("cloud hard nproc -1\n") + cfo.save() + + try: + if self.syscfg.env.svrConf == "Tomcat7": + self.syscfg.svo.disableService("tomcat") + else: + self.syscfg.svo.disableService("tomcat6") + except: + pass + + self.syscfg.svo.stopService("cloudstack-management") + + if self.syscfg.env.noStart == False: + if self.syscfg.svo.enableService("cloudstack-management"): + return True + else: + raise CloudRuntimeException("Failed to configure %s, please see the /var/log/cloudstack/management/setupManagement.log for detail"%self.serviceName) + else: + print "Configured successfully, but not starting management server." + return True diff --git a/cosmic-core/python/lib/cloudutils/syscfg.py b/cosmic-core/python/lib/cloudutils/syscfg.py new file mode 100755 index 0000000000..467fcf190e --- /dev/null +++ b/cosmic-core/python/lib/cloudutils/syscfg.py @@ -0,0 +1,215 @@ +# 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. +from utilities import Distribution, serviceOpsRedhat,serviceOpsUbuntu,serviceOpsRedhat7 +from serviceConfig import * +class sysConfigFactory: + @staticmethod + def getSysConfigFactory(glbEnv): + if glbEnv.mode == "Agent": + return sysConfigAgentFactory.getAgent(glbEnv) + elif glbEnv.mode == "Server": + return sysConfigServerFactory.getServer(glbEnv) + elif glbEnv.mode == "HttpsServer": + return sysConfigServerFactory.getServer(glbEnv) + elif glbEnv.mode == "Db": + return sysConfigDbFactory.getDb(glbEnv) + else: + raise CloudInternalException("Need to specify which mode are u running: Agent/Server/Db") + +class sysConfigAgentFactory: + @staticmethod + def getAgent(glbEnv): + glbEnv.distribution = Distribution() + distribution = glbEnv.distribution.getVersion() + if distribution == "Ubuntu": + return sysConfigAgentUbuntu(glbEnv) + elif distribution == "Fedora" or distribution == "RHEL6": + return sysConfigRedhat6(glbEnv) + elif distribution == "CentOS" or distribution == "RHEL5": + return sysConfigRedhat5(glbEnv) + elif distribution == "RHEL7": + return sysConfigRedhat7(glbEnv) + else: + print "Can't find the distribution version" + return sysConfig() + +class sysConfigServerFactory: + @staticmethod + def getServer(glbEnv): + glbEnv.distribution = Distribution() + distribution = glbEnv.distribution.getVersion() + if distribution == "Ubuntu": + return sysConfigServerUbuntu(glbEnv) + elif distribution != "Unknown": + return sysConfigServerRedhat(glbEnv) + else: + print "Can't find the distribution version" + return sysConfig() + +class sysConfigDbFactory: + @staticmethod + def getDb(glbEnv): + pass + +class sysConfig(object): + def __init__(self, env): + self.env = env + self.services = [] + + def registerService(self, service): + self.services.append(service(self)) + + def config(self): + if not self.check(): + return False + + for service in self.services: + if not service.configration(): + raise CloudInternalException("Configuration failed for service %s" % service.serviceName) + + def restore(self): + for service in self.services: + service.backup() + + def check(self): + return True + +class sysConfigAgent(sysConfig): + def __init__(self, env): + super(sysConfigAgent, self).__init__(env) + + def check(self): + if self.env.debug: + return True + + if self.env.agentMode == "myCloud": + if self.env.distribution.getVersion() != "Ubuntu": + raise CloudInternalException("Need to run myCloud agent on an Ubuntu machine\n") + elif self.env.distribution.getArch() != "x86_64": + raise CloudInternalException("Need to run myCloud agent on an 64bit machine\n") + #check free disk space on the local disk + if os.path.exists("/var/lib/libvirt/images"): + size = -1 + try: + size = int(bash("df -P /var/lib/libvirt/images | tail -1 |awk '{print $4}'").getStdout()) + except: + pass + + if size != -1 and size < (30 * 1024 * 1024): + raise CloudRuntimeException("Need at least 30G free disk space under /var/lib/libvirt/images") + + #check memory + mem = -1 + try: + mem = int(bash("free -g|grep Mem|awk '{print $2}'").getStdout()) + except: + pass + + if mem != -1 and mem < 1: + raise CloudRuntimeException("Need at least 1G memory") + + + if os.geteuid() != 0: + raise CloudInternalException("Need to execute with root permission\n") + + hostname = bash("hostname -f") + if not hostname.isSuccess(): + raise CloudInternalException("Checking hostname ... [Failed]\nPlease edit /etc/hosts, add a Fully Qualified Domain Name as your hostname\n") + + kvmEnabled = self.svo.isKVMEnabled() + if not kvmEnabled: + raise CloudInternalException("Checking KVM...[Failed]\nPlease enable KVM on this machine\n") + + return True + + +class sysConfigAgentRedhatBase(sysConfigAgent): + def __init__(self, env): + self.svo = serviceOpsRedhat() + super(sysConfigAgentRedhatBase, self).__init__(env) + +class sysConfigAgentRedhat7Base(sysConfigAgent): + def __init__(self, env): + self.svo = serviceOpsRedhat7() + super(sysConfigAgentRedhat7Base, self).__init__(env) + +class sysConfigAgentUbuntu(sysConfigAgent): + def __init__(self, glbEnv): + super(sysConfigAgentUbuntu, self).__init__(glbEnv) + self.svo = serviceOpsUbuntu() + + self.services = [securityPolicyConfigUbuntu(self), + networkConfigUbuntu(self), + libvirtConfigUbuntu(self), + firewallConfigUbuntu(self), + nfsConfig(self), + cloudAgentConfig(self)] + +#it covers RHEL6/Fedora13/Fedora14 +class sysConfigRedhat6(sysConfigAgentRedhatBase): + def __init__(self, glbEnv): + super(sysConfigRedhat6, self).__init__(glbEnv) + self.services = [cgroupConfig(self), + securityPolicyConfigRedhat(self), + networkConfigRedhat(self), + libvirtConfigRedhat(self), + firewallConfigAgent(self), + nfsConfig(self), + cloudAgentConfig(self)] + +#It covers RHEL5/CentOS5, the mainly difference is that there is no cgroup +class sysConfigRedhat5(sysConfigAgentRedhatBase): + def __init__(self, glbEnv): + super(sysConfigRedhat5, self).__init__(glbEnv) + self.services = [ + securityPolicyConfigRedhat(self), + networkConfigRedhat(self), + libvirtConfigRedhat(self), + firewallConfigAgent(self), + cloudAgentConfig(self)] + +#it covers RHEL7 +class sysConfigRedhat7(sysConfigAgentRedhat7Base): + def __init__(self, glbEnv): + super(sysConfigRedhat7, self).__init__(glbEnv) + self.services = [securityPolicyConfigRedhat(self), + networkConfigRedhat(self), + libvirtConfigRedhat(self), + firewallConfigAgent(self), + nfsConfig(self), + cloudAgentConfig(self)] + +class sysConfigServer(sysConfig): + def check(self): + if os.geteuid() != 0: + raise CloudInternalException("Need to execute with root permission") + hostname = bash("hostname -f") + if not hostname.isSuccess(): + raise CloudInternalException("Checking hostname ... [Failed]\nPlease edit /etc/hosts, add a Fully Qualified Domain Name as your hostname\n") + return True + +class sysConfigServerRedhat(sysConfigServer): + def __init__(self, glbEnv): + super(sysConfigServerRedhat, self).__init__(glbEnv) + self.svo = serviceOpsRedhat() + self.services = [firewallConfigServer(self)] + +class sysConfigServerUbuntu(sysConfigServer): + def __init__(self, glbEnv): + super(sysConfigServerUbuntu, self).__init__(glbEnv) + self.svo = serviceOpsUbuntu() + self.services = [ubuntuFirewallConfigServer(self)] diff --git a/cosmic-core/python/lib/cloudutils/utilities.py b/cosmic-core/python/lib/cloudutils/utilities.py new file mode 100755 index 0000000000..05a2a58552 --- /dev/null +++ b/cosmic-core/python/lib/cloudutils/utilities.py @@ -0,0 +1,250 @@ +# 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. +from cloudException import CloudRuntimeException, formatExceptionInfo +import logging +from subprocess import PIPE, Popen +from signal import alarm, signal, SIGALRM, SIGKILL +import sys +import os +class bash: + def __init__(self, args, timeout=600): + self.args = args + logging.debug("execute:%s"%args) + self.timeout = timeout + self.process = None + self.success = False + self.run() + + def run(self): + class Alarm(Exception): + pass + def alarm_handler(signum, frame): + raise Alarm + + try: + self.process = Popen(self.args, shell=True, stdout=PIPE, stderr=PIPE) + if self.timeout != -1: + signal(SIGALRM, alarm_handler) + alarm(self.timeout) + + try: + self.stdout, self.stderr = self.process.communicate() + if self.timeout != -1: + alarm(0) + except Alarm: + os.kill(self.process.pid, SIGKILL) + raise CloudRuntimeException("Timeout during command execution") + + self.success = self.process.returncode == 0 + except: + raise CloudRuntimeException(formatExceptionInfo()) + + if not self.success: + logging.debug("Failed to execute:" + self.getErrMsg()) + + def isSuccess(self): + return self.success + + def getStdout(self): + return self.stdout.strip("\n") + + def getLines(self): + return self.stdout.split("\n") + + def getStderr(self): + return self.stderr.strip("\n") + + def getErrMsg(self): + if self.isSuccess(): + return "" + + if self.getStderr() is None or self.getStderr() == "": + return self.getStdout() + else: + return self.getStderr() + +def initLoging(logFile=None): + try: + if logFile is None: + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(filename=logFile, level=logging.DEBUG) + except: + logging.basicConfig(level=logging.DEBUG) + +def writeProgressBar(msg, result): + output = "[%-6s]\n"%"Failed" + if msg is not None: + output = "%-30s"%msg + elif result is True: + output = "[%-2s]\n"%"OK" + elif result is False: + output = "[%-6s]\n"%"Failed" + sys.stdout.write(output) + sys.stdout.flush() + +class UnknownSystemException(Exception): + "This Excption is raised if the current operating enviornment is unknown" + pass + +class Distribution: + def __init__(self): + self.distro = "Unknown" + self.release = "Unknown" + + if os.path.exists("/etc/fedora-release"): + self.distro = "Fedora" + elif os.path.exists("/etc/redhat-release"): + version = file("/etc/redhat-release").readline() + if version.find("Red Hat Enterprise Linux Server release 6") != -1 or version.find("Scientific Linux release 6") != -1 or version.find("CentOS Linux release 6") != -1 or version.find("CentOS release 6.") != -1: + self.distro = "RHEL6" + elif version.find("Red Hat Enterprise Linux Server release 7") != -1 or version.find("Scientific Linux release 7") != -1 or version.find("CentOS Linux release 7") != -1 or version.find("CentOS release 7.") != -1: + self.distro = "RHEL7" + elif version.find("CentOS") != -1: + self.distro = "CentOS" + else: + self.distro = "RHEL5" + elif os.path.exists("/etc/legal") and "Ubuntu" in file("/etc/legal").read(-1): + self.distro = "Ubuntu" + kernel = bash("uname -r").getStdout() + if kernel.find("2.6.32") != -1: + self.release = "10.04" + self.arch = bash("uname -m").getStdout() + elif os.path.exists("/usr/bin/lsb_release"): + o = bash("/usr/bin/lsb_release -i") + distributor = o.getStdout().split(":\t")[1] + if "Debian" in distributor: + # This obviously needs a rewrite at some point + self.distro = "Ubuntu" + else: + raise UnknownSystemException(distributor) + else: + raise UnknownSystemException + + def getVersion(self): + return self.distro + def getRelease(self): + return self.release + def getArch(self): + return self.arch + + +class serviceOps: + pass +class serviceOpsRedhat(serviceOps): + def isServiceRunning(self, servicename): + try: + o = bash("service " + servicename + " status") + if "running" in o.getStdout() or "start" in o.getStdout() or "Running" in o.getStdout(): + return True + else: + return False + except: + return False + + def stopService(self, servicename,force=False): + if self.isServiceRunning(servicename) or force: + return bash("service " + servicename +" stop").isSuccess() + + return True + def disableService(self, servicename): + result = self.stopService(servicename) + bash("chkconfig --del " + servicename) + return result + + def startService(self, servicename,force=False): + if not self.isServiceRunning(servicename) or force: + return bash("service " + servicename + " start").isSuccess() + return True + + def enableService(self, servicename,forcestart=False): + bash("chkconfig --level 2345 " + servicename + " on") + return self.startService(servicename,force=forcestart) + + def isKVMEnabled(self): + if os.path.exists("/dev/kvm"): + return True + else: + return False + +class serviceOpsUbuntu(serviceOps): + def isServiceRunning(self, servicename): + try: + o = bash("sudo /usr/sbin/service " + servicename + " status") + if "not running" in o.getStdout(): + return False + else: + return True + except: + return False + + def stopService(self, servicename,force=True): + if self.isServiceRunning(servicename) or force: + return bash("sudo /usr/sbin/service " + servicename +" stop").isSuccess() + + def disableService(self, servicename): + result = self.stopService(servicename) + bash("sudo update-rc.d -f " + servicename + " remove") + return result + + def startService(self, servicename,force=True): + if not self.isServiceRunning(servicename) or force: + return bash("sudo /usr/sbin/service " + servicename + " start").isSuccess() + + def enableService(self, servicename,forcestart=True): + bash("sudo update-rc.d -f " + servicename + " remove") + bash("sudo update-rc.d -f " + servicename + " defaults") + return self.startService(servicename,force=forcestart) + + def isKVMEnabled(self): + return bash("kvm-ok").isSuccess() + +class serviceOpsRedhat7(serviceOps): + def isServiceRunning(self, servicename): + try: + o = bash("systemctl status " + servicename) + if "running" in o.getStdout() or "start" in o.getStdout() or "Running" in o.getStdout(): + return True + else: + return False + except: + return False + + def stopService(self, servicename,force=False): + if self.isServiceRunning(servicename) or force: + return bash("systemctl stop " + servicename).isSuccess() + + return True + def disableService(self, servicename): + result = self.stopService(servicename) + bash("systemctl disable " + servicename) + return result + + def startService(self, servicename,force=False): + if not self.isServiceRunning(servicename) or force: + return bash("systemctl start " + servicename).isSuccess() + return True + + def enableService(self, servicename,forcestart=False): + bash("systemctl enable " + servicename) + return self.startService(servicename,force=forcestart) + + def isKVMEnabled(self): + if os.path.exists("/dev/kvm"): + return True + else: + return False diff --git a/cosmic-core/scripts/common/keys/ssl-keys.py b/cosmic-core/scripts/common/keys/ssl-keys.py new file mode 100644 index 0000000000..d6804cca19 --- /dev/null +++ b/cosmic-core/scripts/common/keys/ssl-keys.py @@ -0,0 +1,58 @@ +#!/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. + + +# Copies keys that enable SSH communication with system vms +# $1 = new public key +# $2 = new private key +''' +All imports go here... +''' +from subprocess import call +import socket +import sys +import os +import subprocess +import traceback + +def generateSSLKey(outputPath): + logf = open("ssl-keys.log", "w") + hostName = socket.gethostbyname(socket.gethostname()) + keyFile = outputPath + os.sep + "cloudmanagementserver.keystore" + logf.write("HostName = %s\n" % hostName) + logf.write("OutputPath = %s\n" % keyFile) + dname='cn="Cloudstack User",ou="' + hostName + '",o="' + hostName + '",c="Unknown"'; + logf.write("dname = %s\n" % dname) + logf.flush() + try : + return_code = subprocess.Popen(["keytool", "-genkey", "-keystore", keyFile, "-storepass", "vmops.com", "-keypass", "vmops.com", "-keyalg", "RSA", "-validity", "3650", "-dname", dname],shell=True,stdout=logf, stderr=logf) + return_code.wait() + except OSError as e: + logf.flush() + traceback.print_exc(file=logf) + logf.flush() + logf.write("SSL key generated is : %s" % return_code) + logf.flush() + +argsSize=len(sys.argv) +if argsSize != 2: + print("Usage: ssl-keys.py ") + sys.exit(None) +sslKeyPath=sys.argv[1] + +generateSSLKey(sslKeyPath) \ No newline at end of file diff --git a/cosmic-core/scripts/installer/createtmplt.sh b/cosmic-core/scripts/installer/createtmplt.sh new file mode 100755 index 0000000000..67b25c3b92 --- /dev/null +++ b/cosmic-core/scripts/installer/createtmplt.sh @@ -0,0 +1,277 @@ +#!/usr/bin/env 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. + + +# $Id: createtmplt.sh 9132 2010-06-04 20:17:43Z manuel $ $HeadURL: svn://svn.lab.vmops.com/repos/vmdev/java/scripts/installer/createtmplt.sh $ +# createtmplt.sh -- install a template + +usage() { + printf "Usage: %s: -t -n -f -s -c -d -h [-u]\n" $(basename $0) >&2 +} + + +#set -x + +rollback_if_needed() { + if [ $2 -gt 0 ] + then + printf "$3\n" + #back out all changes + zfs destroy -r $1 + exit 2 +fi +} + +verify_cksum() { + digestalgo="" + case ${#1} in + 32) digestalgo="md5sum" ;; + 40) digestalgo="sha1sum" ;; + 56) digestalgo="sha224sum" ;; + 64) digestalgo="sha256sum" ;; + 96) digestalgo="sha384sum" ;; + 128) digestalgo="sha512sum" ;; + *) echo "Please provide valid cheksum" ; exit 3 ;; + esac + echo "$1 $2" | $digestalgo -c --status + #printf "$1\t$2" | $digestalgo -c --status + if [ $? -gt 0 ] + then + printf "Checksum failed, not proceeding with install\n" + exit 3 + fi +} + +untar() { + local ft=$(file $1| awk -F" " '{print $2}') + local basedir=$(dirname $1) + local tarfile=$(basename $1) + case $ft in + USTAR) local rootimg=$(tar tf $1 | grep $3) + (cp $1 $2; cd $2; tar xf $tarfile) + rm -f $1 + printf "$2/$rootimg" + ;; + *) printf "$1" + return 0 + ;; + esac + +} + +uncompress() { + local ft=$(file $1| awk -F" " '{print $2}') + local imgfile=${1%.*} #strip out trailing file suffix + local tmpfile=${imgfile}.tmp + + case $ft in + gzip) gunzip -c $1 > $tmpfile + ;; + bzip2) bunzip2 -c $1 > $tmpfile + ;; + [zZ][iI][pP]) unzip -p $1 | cat > $tmpfile + ;; + *) printf "$1" + return 0 + ;; + esac + + if [ $? -gt 0 ] + then + printf "Failed to uncompress file, exiting " + exit 1 + fi + + mv $tmpfile $imgfile + printf "$imgfile" + + return 0 +} + +create_from_file() { + local tmpltfs=$1 + local tmpltimg=$2 + local tgtfile=$3 + local volsize=$4 + local cleanup=$5 + + #copy 64k of zeros for LUN metatdata + dd if=/dev/zero of=/$tgtfile bs=64k count=1 + + #copy the file to the disk + dd if=$tmpltimg of=/$tgtfile bs=64k seek=1 + + rollback_if_needed $tmpltfs $? "Failed to copy root disk" + + if [ "$cleanup" == "true" ] + then + rm -f $tmpltimg + fi +} + + +tflag= +nflag= +fflag= +sflag= +hflag= +hvm=false +cleanup=false +dflag= +cflag= + +while getopts 'uht:n:f:s:c:d:' OPTION +do + case $OPTION in + t) tflag=1 + tmpltfs="$OPTARG" + ;; + n) nflag=1 + tmpltname="$OPTARG" + ;; + f) fflag=1 + tmpltimg="$OPTARG" + ;; + s) sflag=1 + volsize="$OPTARG" + ;; + c) cflag=1 + cksum="$OPTARG" + ;; + d) dflag=1 + descr="$OPTARG" + ;; + h) hflag=1 + hvm="true" + ;; + u) cleanup="true" + ;; + ?) usage + exit 2 + ;; + esac +done + +if [ "$tflag$nflag$fflag$sflag" != "1111" ] +then + usage + exit 2 +fi + +if [ -n "$cksum" ] +then + verify_cksum $cksum $tmpltimg +fi + +if [ ${tmpltfs:0:1} == / ] +then + tmpltfs=${tmpltfs:1} +fi + +if [ ! -d /$tmpltfs ] +then + zfs create -p $tmpltfs + if [ $? -gt 0 ] + then + printf "Failed to create user fs $tmpltfs\n" >&2 + exit 1 + fi +fi + +if [[ $(zfs get -H -o value -p type $tmpltfs) != filesystem ]] +then + printf "template fs doesn't exist\n" >&2 + exit 2 +fi + +tmpltimg2=$(uncompress $tmpltimg) +tmpltimg2=$(untar $tmpltimg2 /$tmpltfs vmi-root) + +if [ ! -f $tmpltimg2 ] +then + rollback_if_needed $tmpltfs 2 "root disk file $tmpltimg doesn't exist\n" + exit 3 +fi + +# need the 'G' suffix on volume size +if [ ${volsize:(-1)} != G ] +then + volsize=${volsize}G +fi + +#determine source file size -- it needs to be less than or equal to volsize +imgsize=$(ls -lh $tmpltimg2| awk -F" " '{print $5}') +if [ ${imgsize:(-1)} == G ] +then + imgsize=${imgsize%G} #strip out the G + imgsize=${imgsize%.*} #...and any decimal part + let imgsize=imgsize+1 # add 1 to compensate for decimal part + volsizetmp=${volsize%G} + if [ $volsizetmp -lt $imgsize ] + then + volsize=${imgsize}G + fi +fi + +tgtfile=${tmpltfs}/vmi-root-${tmpltname} + +create_from_file $tmpltfs $tmpltimg2 $tgtfile $volsize $cleanup + +tmpltswap=$(ls -lh /$tmpltfs | grep swap) +if [ $? -eq 0 ] +then + swapsize=$(echo $tmpltswap | awk '{print $5}') + tmpltswap=$(echo $tmpltswap | awk '{print $NF}') + tmpltswap=/${tmpltfs}/${tmpltswap} + tgtfile=${tmpltfs}/vmi-swap-${tmpltname} + create_from_file $tmpltfs $tmpltswap $tgtfile $swapsize $cleanup +fi + + +if [ "$hvm" != "true" ] +then + vmlinuz=$(ls /$tmpltfs/vmlinuz*) + if [ "$vmlinuz" == "" ] + then + touch /$tmpltfs/pygrub + fi +fi + +rollback_if_needed $tmpltfs $? "Failed to create pygrub file" + +touch /$tmpltfs/template.properties +rollback_if_needed $tmpltfs $? "Failed to create template.properties file" +echo -n "" > /$tmpltfs/template.properties + +today=$(date '+%m_%d_%Y') +echo "snapshot.name=$today" > /$tmpltfs/template.properties +echo "description=$descr" >> /$tmpltfs/template.properties +echo "name=$tmpltname" >> /$tmpltfs/template.properties +echo "checksum=$cksum" >> /$tmpltfs/template.properties +echo "hvm=$hvm" >> /$tmpltfs/template.properties +echo "volume.size=$volsize" >> /$tmpltfs/template.properties + +zfs snapshot -r $tmpltfs@vmops_ss +rollback_if_needed $tmpltfs $? "Failed to snapshot filesystem" + +#if [ "$cleanup" == "true" ] +#then + #rm -f $tmpltimg +#fi + +exit 0 diff --git a/cosmic-core/scripts/installer/createvolume.sh b/cosmic-core/scripts/installer/createvolume.sh new file mode 100755 index 0000000000..00ee5e5e77 --- /dev/null +++ b/cosmic-core/scripts/installer/createvolume.sh @@ -0,0 +1,278 @@ +#!/usr/bin/env 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. + + + +# $Id: createvol.sh 9132 2010-06-04 20:17:43Z manuel $ $HeadURL: svn://svn.lab.vmops.com/repos/vmdev/java/scripts/installer/createvolume.sh $ +# createvolume.sh -- install a volume + +usage() { + printf "Usage: %s: -t -n -f -s -c -d -h [-u]\n" $(basename $0) >&2 +} + + +#set -x + +rollback_if_needed() { + if [ $2 -gt 0 ] + then + printf "$3\n" + #back out all changes + zfs destroy -r $1 + exit 2 +fi +} + +verify_cksum() { + digestalgo="" + case ${#1} in + 32) digestalgo="md5sum" ;; + 40) digestalgo="sha1sum" ;; + 56) digestalgo="sha224sum" ;; + 64) digestalgo="sha256sum" ;; + 96) digestalgo="sha384sum" ;; + 128) digestalgo="sha512sum" ;; + *) echo "Please provide valid cheksum" ; exit 3 ;; + esac + echo "$1 $2" | $digestalgo -c --status + #printf "$1\t$2" | $digestalgo -c --status + if [ $? -gt 0 ] + then + printf "Checksum failed, not proceeding with install\n" + exit 3 + fi +} + +untar() { + local ft=$(file $1| awk -F" " '{print $2}') + local basedir=$(dirname $1) + local tarfile=$(basename $1) + case $ft in + USTAR) local rootimg=$(tar tf $1 | grep $3) + (cp $1 $2; cd $2; tar xf $tarfile) + rm -f $1 + printf "$2/$rootimg" + ;; + *) printf "$1" + return 0 + ;; + esac + +} + +uncompress() { + local ft=$(file $1| awk -F" " '{print $2}') + local imgfile=${1%.*} #strip out trailing file suffix + local tmpfile=${imgfile}.tmp + + case $ft in + gzip) gunzip -c $1 > $tmpfile + ;; + bzip2) bunzip2 -c $1 > $tmpfile + ;; + ZIP) unzip -p $1 | cat > $tmpfile + ;; + *) printf "$1" + return 0 + ;; + esac + + if [ $? -gt 0 ] + then + printf "Failed to uncompress file, exiting " + exit 1 + fi + + mv $tmpfile $imgfile + printf "$imgfile" + + return 0 +} + +create_from_file() { + local volfs=$1 + local volimg=$2 + local tgtfile=$3 + local volsize=$4 + local cleanup=$5 + + #copy 64k of zeros for LUN metatdata + dd if=/dev/zero of=/$tgtfile bs=64k count=1 + + #copy the file to the disk + dd if=$volimg of=/$tgtfile bs=64k seek=1 + + rollback_if_needed $volfs $? "Failed to copy root disk" + + if [ "$cleanup" == "true" ] + then + rm -f $volimg + fi +} + + +tflag= +nflag= +fflag= +sflag= +hflag= +hvm=false +cleanup=false +dflag= +cflag= + +while getopts 'uht:n:f:s:c:d:' OPTION +do + case $OPTION in + t) tflag=1 + volfs="$OPTARG" + ;; + n) nflag=1 + volname="$OPTARG" + ;; + f) fflag=1 + volimg="$OPTARG" + ;; + s) sflag=1 + volsize="$OPTARG" + ;; + c) cflag=1 + cksum="$OPTARG" + ;; + d) dflag=1 + descr="$OPTARG" + ;; + h) hflag=1 + hvm="true" + ;; + u) cleanup="true" + ;; + ?) usage + exit 2 + ;; + esac +done + +if [ "$tflag$nflag$fflag$sflag" != "1111" ] +then + usage + exit 2 +fi + +if [ -n "$cksum" ] +then + verify_cksum $cksum $volimg +fi + +if [ ${volfs:0:1} == / ] +then + volfs=${volfs:1} +fi + +if [ ! -d /$volfs ] +then + zfs create -p $volfs + if [ $? -gt 0 ] + then + printf "Failed to create user fs $volfs\n" >&2 + exit 1 + fi +fi + +if [[ $(zfs get -H -o value -p type $volfs) != filesystem ]] +then + printf "volume fs doesn't exist\n" >&2 + exit 2 +fi + +volimg2=$(uncompress $volimg) +volimg2=$(untar $volimg2 /$volfs vmi-root) + +if [ ! -f $volimg2 ] +then + rollback_if_needed $volfs 2 "root disk file $volimg doesn't exist\n" + exit 3 +fi + +# need the 'G' suffix on volume size +if [ ${volsize:(-1)} != G ] +then + volsize=${volsize}G +fi + +#determine source file size -- it needs to be less than or equal to volsize +imgsize=$(ls -lh $volimg2| awk -F" " '{print $5}') +if [ ${imgsize:(-1)} == G ] +then + imgsize=${imgsize%G} #strip out the G + imgsize=${imgsize%.*} #...and any decimal part + let imgsize=imgsize+1 # add 1 to compensate for decimal part + volsizetmp=${volsize%G} + if [ $volsizetmp -lt $imgsize ] + then + volsize=${imgsize}G + fi +fi + +tgtfile=${volfs}/vmi-root-${volname} + +create_from_file $volfs $volimg2 $tgtfile $volsize $cleanup + +volswap=$(ls -lh /$volfs | grep swap) +if [ $? -eq 0 ] +then + swapsize=$(echo $volswap | awk '{print $5}') + volswap=$(echo $volswap | awk '{print $NF}') + volswap=/${volfs}/${volswap} + tgtfile=${volfs}/vmi-swap-${volname} + create_from_file $volfs $volswap $tgtfile $swapsize $cleanup +fi + + +if [ "$hvm" != "true" ] +then + vmlinuz=$(ls /$volfs/vmlinuz*) + if [ "$vmlinuz" == "" ] + then + touch /$volfs/pygrub + fi +fi + +rollback_if_needed $volfs $? "Failed to create pygrub file" + +touch /$volfs/volume.properties +rollback_if_needed $volfs $? "Failed to create volume.properties file" +echo -n "" > /$volfs/volume.properties + +today=$(date '+%m_%d_%Y') +echo "snapshot.name=$today" > /$volfs/volume.properties +echo "description=$descr" >> /$volfs/volume.properties +echo "name=$volname" >> /$volfs/volume.properties +echo "checksum=$cksum" >> /$volfs/volume.properties +echo "hvm=$hvm" >> /$volfs/volume.properties +echo "volume.size=$volsize" >> /$volfs/volume.properties + +zfs snapshot -r $volfs@vmops_ss +rollback_if_needed $volfs $? "Failed to snapshot filesystem" + +#if [ "$cleanup" == "true" ] +#then + #rm -f $volimg +#fi + +exit 0 diff --git a/cosmic-core/scripts/installer/installcentos.sh b/cosmic-core/scripts/installer/installcentos.sh new file mode 100755 index 0000000000..e68e0577a8 --- /dev/null +++ b/cosmic-core/scripts/installer/installcentos.sh @@ -0,0 +1,74 @@ +#!/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. + + +# $Id: installcentos.sh 9132 2010-06-04 20:17:43Z manuel $ $HeadURL: svn://svn.lab.vmops.com/repos/vmdev/java/scripts/installer/installcentos.sh $ + +# set -x +usage() { + echo "Usage: $(basename $0) -t -c " + echo "eg: $(basename $0) -t tank/volumes/demo/template -c /root/createmplt.sh " +} + +fflag= +tflag= +cflag= +while getopts 'f:t:c:' OPTION +do + case $OPTION in + f) fflag=1 + ;; + t) tflag=1 + template_location="$OPTARG" + ;; + c) cflag=1 + create_tmplt_path="$OPTARG" + ;; + ?) usage + exit 2 + ;; + esac +done + +shift $(($OPTIND - 1)) + +if [ "$tflag" != "1" ] || [ "$cflag" != "1" ] +then + usage + exit 2 +fi + +tmpltfs=$template_location/public/os/centos53-x86_64 +if [ "$fflag" == "1" ] +then + zfs destroy -Rr $tmpltfs 2> /dev/null +fi + +snaps=$(zfs list -t snapshot -r $tmpltfs 2> /dev/null) +if [ $? -eq 0 -a "$snaps" != "" ] +then + echo "Warning: some snapshots already exist at target location $tmpltfs" + echo "Use -f to delete these first" + exit 2 +fi + +$create_tmplt_path -t $tmpltfs -n centos53-x86_64 -f /root/template/vmi-root-centos.5-3.x86-64.img.bz2 -s 2 -d "centos5.3-x86_64" -u + +rm -f /$tmpltfs/*.tar + +exit 0 diff --git a/cosmic-core/scripts/installer/installdomp.sh b/cosmic-core/scripts/installer/installdomp.sh new file mode 100755 index 0000000000..79ea9fda45 --- /dev/null +++ b/cosmic-core/scripts/installer/installdomp.sh @@ -0,0 +1,67 @@ +#!/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. + + +# $Id: installdomp.sh 9132 2010-06-04 20:17:43Z manuel $ $HeadURL: svn://svn.lab.vmops.com/repos/vmdev/java/scripts/installer/installdomp.sh $ + +#set -x +usage() { + echo "Usage: $(basename $0) " + echo "eg: $(basename $0) tank/volumes/demo/template" +} + +fflag= + +while getopts 'f' OPTION +do + case $OPTION in + f) fflag=1 + ;; + ?) usage + exit 2 + ;; + esac +done + +shift $(($OPTIND - 1)) + +if [ $# -lt 1 ] +then + usage + exit 2 +fi + +tmpltfs=$1/private/u000000/os/consoleproxy +if [ "$fflag" == "1" ] +then + zfs destroy -r $tmpltfs 2> /dev/null +fi + +snaps=$(zfs list -t snapshot -r $tmpltfs) +if [ $? -eq 0 -a "$snaps" != "no datasets available" ] +then + echo "Warning: some snapshots already exist at target location $tmpltfs" + echo "Use -f to delete these first" + exit 2 +fi + +$(dirname $0)/createtmplt.sh -t $tmpltfs -n consoleproxy -f /root/template/vmi-root-fc8-x86_64-domP.img.bz2 -s 3 -d consoleproxy -u + +rm -f /$tmpltfs/consoleproxy.tar + +exit 0 diff --git a/cosmic-core/scripts/installer/run_installer.sh b/cosmic-core/scripts/installer/run_installer.sh new file mode 100755 index 0000000000..8452e3773c --- /dev/null +++ b/cosmic-core/scripts/installer/run_installer.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env 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. + + +# $Id: run_installer.sh 9132 2010-06-04 20:17:43Z manuel $ $HeadURL: svn://svn.lab.vmops.com/repos/vmdev/java/scripts/installer/run_installer.sh $ + +installer=$1 +classname="com.vmops.installer.VMOpsSimpleSetup" + +if [ "$installer" != "routing" ] +then + if [ "$installer" != "storage" ] + then + echo "Valid installers: routing/storage" + exit 1 + fi +fi + +if [ "$VMOPS_HOME" == "" ] +then + VMOPS_HOME="/usr/local/vmops" +fi + +export LD_LIBRARY_PATH=$(pwd)/lib:$LD_LIBRARY_PATH +java -cp $VMOPS_HOME/agent/log4j-1.2.15.jar:$VMOPS_HOME/agent/apache-log4j-extras-1.0.jar:$VMOPS_HOME/agent/commons-logging-1.1.1.jar:$VMOPS_HOME/agent/charva.jar:$VMOPS_HOME/agent/vmops-utils.jar:$VMOPS_HOME/agent/vmops-installer.jar:$VMOPS_HOME/agent/conf $classname $* diff --git a/cosmic-core/scripts/network/domr/router_proxy.sh b/cosmic-core/scripts/network/domr/router_proxy.sh new file mode 100755 index 0000000000..f9cb7ca015 --- /dev/null +++ b/cosmic-core/scripts/network/domr/router_proxy.sh @@ -0,0 +1,47 @@ +#!/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. + +# used as a proxy to call script inside virtual router + +#set -x + +check_gw() { + ping -c 1 -n -q $1 > /dev/null + if [ $? -gt 0 ] + then + sleep 1 + ping -c 1 -n -q $1 > /dev/null + fi + if [ $? -gt 0 ] + then + exit 1 + fi +} + +cert="/root/.ssh/id_rsa.cloud" + +script=$1 +shift + +domRIp=$1 +shift + +check_gw "$domRIp" + +ssh -p 3922 -q -o StrictHostKeyChecking=no -i $cert root@$domRIp "/opt/cloud/bin/$script $*" +exit $? diff --git a/cosmic-core/scripts/network/exdhcp/dhcpd_edithosts.py b/cosmic-core/scripts/network/exdhcp/dhcpd_edithosts.py new file mode 100644 index 0000000000..d87ca3dc8a --- /dev/null +++ b/cosmic-core/scripts/network/exdhcp/dhcpd_edithosts.py @@ -0,0 +1,127 @@ +#!/usr/bin/python +# 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. + + +# Usage: dhcpd_edithosts.py mac ip hostname dns gateway nextserver +import os +import sys +from os import remove +from os.path import exists +from time import sleep + +usage = '''dhcpd_edithosts.py mac ip hostname dns gateway nextserver''' +conf_path = "/etc/dhcpd.conf" +file_lock = "/etc/dhcpd.conf_locked" +sleep_max = 20 +host_entry = 'host %s { hardware ethernet %s; fixed-address %s; option domain-name-servers %s; option domain-name "%s"; option routers %s; default-lease-time infinite; max-lease-time infinite; min-lease-time infinite;}' +host_entry1 = 'host %s { hardware ethernet %s; fixed-address %s; option domain-name-servers %s; option domain-name "%s"; option routers %s; default-lease-time infinite; max-lease-time infinite; min-lease-time infinite; next-server %s;}' + + +def lock(): + if exists(file_lock): + count = 0 + while (exists(file_lock)): + sleep(1) + count = count + 1 + if count > sleep_max: + print "Can not get file lock at %s, time expired" % file_lock + return False + + try: + f = open(file_lock, "w") + f.close() + return True + except IOError, e: + print "Cannot create file lock at /etc/dhcpd.conf_locked,", e + return False + + +def unlock(): + if exists(file_lock) == False: + print "Cannot find %s when unlocking, race condition happens" % file_lock + else: + try: + remove(file_lock) + return True + except IOError, e: + print "Cannot remove file lock at %s" % file_lock + return False + + +def insert_host_entry(mac, ip, hostname, dns, gateway, next_server): + if lock() == False: + return 1 + + cmd = 'sed -i /"fixed-address %s"/d %s' % (ip, conf_path) + ret = os.system(cmd) + if ret != 0: + print "Command %s failed" % cmd + unlock() + return 1 + + cmd = 'sed -i /"hardware ethernet %s"/d %s' % (mac, conf_path) + ret = os.system(cmd) + if ret != 0: + print "Command %s failed" % cmd + unlock() + return 1 + + if next_server != "null": + entry = host_entry1 % (hostname, mac, ip, dns, "cloudnine.internal", gateway, next_server) + else: + entry = host_entry % (hostname, mac, ip, dns, "cloudnine.internal", gateway) + cmd = '''echo '%s' >> %s''' % (entry, conf_path) + ret = os.system(cmd) + if ret != 0: + print "Command %s failed" % cmd + unlock() + return 1 + + cmd = 'service dhcpd restart' + ret = os.system(cmd) + if ret != 0: + print "Command %s failed" % cmd + unlock() + return 1 + + if unlock() == False: + return 1 + + return 0 + + +if __name__ == "__main__": + if len(sys.argv) < 7: + print usage + sys.exit(1) + + mac = sys.argv[1] + ip = sys.argv[2] + hostname = sys.argv[3] + dns = sys.argv[4] + gateway = sys.argv[5] + next_server = sys.argv[6] + + if exists(conf_path) == False: + conf_path = "/etc/dhcp/dhcpd.conf" + if exists(conf_path) == False: + print "Cannot find dhcpd.conf" + sys.exit(1) + + ret = insert_host_entry(mac, ip, hostname, dns, gateway, next_server) + sys.exit(ret) diff --git a/cosmic-core/scripts/network/exdhcp/dnsmasq_edithosts.sh b/cosmic-core/scripts/network/exdhcp/dnsmasq_edithosts.sh new file mode 100755 index 0000000000..7990356edc --- /dev/null +++ b/cosmic-core/scripts/network/exdhcp/dnsmasq_edithosts.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env 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. + + +# edithosts.sh -- edit the dhcphosts file on the routing domain +# $1 : the mac address +# $2 : the associated ip address +# $3 : the hostname + +wait_for_dnsmasq () { + local _pid=$(pidof dnsmasq) + for i in 0 1 2 3 4 5 6 7 8 9 10 + do + sleep 1 + _pid=$(pidof dnsmasq) + [ "$_pid" != "" ] && break; + done + [ "$_pid" != "" ] && return 0; + echo "edithosts: timed out waiting for dnsmasq to start" + return 1 +} + +command -v dhcp_release > /dev/null 2>&1 +no_dhcp_release=$? + +[ ! -f /etc/dhcphosts.txt ] && touch /etc/dhcphosts.txt +[ ! -f /var/lib/misc/dnsmasq.leases ] && touch /var/lib/misc/dnsmasq.leases + +sed -i /$1/d /etc/dhcphosts.txt +sed -i /$2,/d /etc/dhcphosts.txt +sed -i /$3,/d /etc/dhcphosts.txt + +echo "$1,$2,$3,infinite" >>/etc/dhcphosts.txt + +#release previous dhcp lease if present +if [ $no_dhcp_release -eq 0 ] +then + dhcp_release lo $2 $(grep $2 $DHCP_LEASES | awk '{print $2}') > /dev/null 2>&1 +fi + +#delete leases to supplied mac and ip addresses +sed -i /$1/d /var/lib/misc/dnsmasq.leases +sed -i /"$2 "/d /var/lib/misc/dnsmasq.leases +sed -i /"$3 "/d /var/lib/misc/dnsmasq.leases + +#put in the new entry +echo "0 $1 $2 $3 *" >> /var/lib/misc/dnsmasq.leases + +#edit hosts file as well +sed -i /"$2 "/d /etc/hosts +sed -i /"$3"/d /etc/hosts +echo "$2 $3" >> /etc/hosts + +# make dnsmasq re-read files +pid=$(pidof dnsmasq) +if [ "$pid" != "" ] +then + # use SIGHUP to avoid service outage if dhcp_release is available. + if [ $no_dhcp_release -eq 0 ] + then + kill -HUP $pid + else + service dnsmasq restart + fi +else + service dnsmasq start + wait_for_dnsmasq +fi + +exit $? diff --git a/cosmic-core/scripts/network/exdhcp/prepare_dhcpd.sh b/cosmic-core/scripts/network/exdhcp/prepare_dhcpd.sh new file mode 100755 index 0000000000..e49d0fa175 --- /dev/null +++ b/cosmic-core/scripts/network/exdhcp/prepare_dhcpd.sh @@ -0,0 +1,68 @@ +#!/bin/sh +# 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. + + +# usage prepare_ping.sh subnet + +dhcpd_conf= +subnet=$1 + +exit_with_error() { + echo $1 + exit 1 +} + +exit_if_fail() { + [ $? -ne 0 ] && exit_with_error "$*" +} + +config_dhcpd() { + echo "$*" >> $dhcpd_conf + [ $? -ne 0 ] && exit_with_error "echo $* failed" +} + +[ $# -ne 1 ] && exit_with_error "Usage:prepare_ping.sh subnet" + +if [ -f "/etc/dhcp/dhcpd.conf" ]; then + dhcpd_conf="/etc/dhcp/dhcpd.conf" +fi + +if [ x"$dhcpd_conf" == "x" ] && [ -f "/etc/dhcpd.conf" ]; then + dhcpd_conf="/etc/dhcpd.conf" +fi + +if [ x"$dhcpd_conf" == "x" ]; then + exit_with_error "Cannot find dhcpd.conf" +fi + +signature=`head -n 1 $dhcpd_conf` +if [ x"$signature" != x"# CloudStack" ]; then + # prepare dhcpd + cp $dhcpd_conf /etc/dhcpd.conf.bak -f + exit_if_fail "Cannot back dhcpd.conf" + echo "# CloudStack" > $dhcpd_conf + echo "# This is produced by CloudStack" >> $dhcpd_conf + config_dhcpd ddns-update-style interim\; + config_dhcpd subnet $subnet netmask 255.255.255.0 {} + config_dhcpd allow booting\; + config_dhcpd allow bootp\; +fi + +service dhcpd restart +exit_if_fail "service dhcpd restart failed" +exit 0 diff --git a/cosmic-core/scripts/network/exdhcp/prepare_dnsmasq.sh b/cosmic-core/scripts/network/exdhcp/prepare_dnsmasq.sh new file mode 100755 index 0000000000..70aa587e60 --- /dev/null +++ b/cosmic-core/scripts/network/exdhcp/prepare_dnsmasq.sh @@ -0,0 +1,241 @@ +#!/bin/sh +# 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. + + +# prepare dnsmasq on external dhcp server +# Usage: +# sh prepare_dnsmasq gateway dns self_ip + +gateway=$1 +dns=$2 +self_ip=$3 + +exit_with_error() { + echo $1 + exit 1 +} + +config_dnsmasq() { + echo "$*" >> /etc/dnsmasq.conf + [ $? -ne 0 ] && exit_with_error "echo $* failed" +} + +[ $# -ne 3 ] && exit_with_error "Usage: prepare_dnsmasq gateway dns self_ip" + +[ -f /etc/dnsmasq.conf ] || exit_with_error "Can not found /etc/dnsmasq.conf" + +touch /var/log/dnsmasq.log +[ $? -ne 0 ] && exit_with_error "touch /var/log/dnsmasq.log failed" +touch /etc/dnsmasq-resolv.conf +[ $? -ne 0 ] && exit_with_error "touch /etc/dnsmasq-resolv.conf failed" +echo "nameserver $dns">/etc/dnsmasq-resolv.conf +[ $? -ne 0 ] && exit_with_error "echo \"nameserver $dns\">/etc/dnsmasq-resolv.conf failed" +touch /var/lib/dnsmasq.trace +[ $? -ne 0 ] && exit_with_error "touch /var/lib/dnsmasq.trace failed" + + +#produce echoer.sh +cat > /usr/bin/echoer.sh<<'EOF' +#!/bin/sh + +sed -i /"$*"/d /var/lib/dnsmasq.trace +echo "$*" >> /var/lib/dnsmasq.trace +EOF +[ $? -ne 0 ] && exit_with_error "can't produce /usr/bin/echoer.sh" + +#produce lease_checker.sh +cat > /usr/bin/lease_checker.sh<<'EOF' +#!/bin/sh +# Usage: lease_checker dhcp_entry_state(add/old/del) mac ip +state=$1 +mac=$2 +ip=$3 + +exit_with_error() { + echo $1 + exit $2 +} + +[ $# -ne 3 ] && exit_with_error "Wrong arguments.Usage: lease_checker dhcp_entry_state(add/old/del) mac ip" -3 + +[ -f /var/lib/dnsmasq.trace ] || exit_with_error "Cannot find /var/lib/dnsmasq" -1 +pidof dnsmasq &>/dev/null +[ $? -ne 0 ] && exit_with_error "Dnsmasq is not running" -2 + +grep "$state $mac $ip" /var/lib/dnsmasq.trace +if [ $? -ne 0 ]; then + exit $? +else + sed -i /"$state $mac $ip"/d /var/lib/dnsmasq.trace + exit 0 +fi + +EOF + +chmod +x /usr/bin/echoer.sh +[ $? -ne 0 ] && exit_with_error "chmod +x /usr/bin/echoer.sh failed" + +# Configure dnsmasq with comments +echo "# This is produced by CloudStack" > /etc/dnsmasq.conf +config_dnsmasq "# Never forward plain names (without a dot or domain part)" +config_dnsmasq domain-needed +config_dnsmasq "# Never forward addresses in the non-routed address spaces." +config_dnsmasq bogus-priv +config_dnsmasq " +# Change this line if you want dns to get its upstream servers from +# somewhere other that /etc/resolv.conf" +config_dnsmasq resolv-file=/etc/dnsmasq-resolv.conf +config_dnsmasq " +# Add local-only domains here, queries in these domains are answered +# from /etc/hosts or DHCP only." +config_dnsmasq local=/cloudnine.internal/ +config_dnsmasq " +# On systems which support it, dnsmasq binds the wildcard address, +# even when it is listening on only some interfaces. It then discards +# requests that it shouldn't reply to. This has the advantage of +# working even when interfaces come and go and change address. If you +# want dnsmasq to really bind only the interfaces it is listening on, +# uncomment this option. About the only time you may need this is when +# running another nameserver on the same machine." +config_dnsmasq bind-interfaces +config_dnsmasq " +# Set this (and domain: see below) if you want to have a domain +# automatically added to simple names in a hosts-file." +config_dnsmasq expand-hosts +config_dnsmasq " +# does the following things. +# 1) Allows DHCP hosts to have fully qualified domain names, as long +# as the domain part matches this setting. +# 2) Sets the \"domain\" DHCP option thereby potentially setting the +# domain of all systems configured by DHCP +# 3) Provides the domain part for \"expand-hosts\" +" +config_dnsmasq domain=cloudnine.internal +config_dnsmasq " +# Send options to hosts which ask for a DHCP lease. +# See RFC 2132 for details of available options. +# Common options can be given to dnsmasq by name: +# run \"dnsmasq --help dhcp\" to get a list. +# Note that all the common settings, such as netmask and +# broadcast address, DNS server and default route, are given +# sane defaults by dnsmasq. You very likely will not need +# any dhcp-options. If you use Windows clients and Samba, there +# are some options which are recommended, they are detailed at the +# end of this section. + +# Override the default route supplied by dnsmasq, which assumes the +# router is the same machine as the one running dnsmasq." +config_dnsmasq dhcp-option=option:router,$gateway +config_dnsmasq " +# Uncomment this to enable the integrated DHCP server, you need +# to supply the range of addresses available for lease and optionally +# a lease time. If you have more than one network, you will need to +# repeat this for each network on which you want to supply DHCP +# service." +config_dnsmasq dhcp-range=$self_ip,static +config_dnsmasq dhcp-hostsfile=/etc/dhcphosts.txt +config_dnsmasq "# Set the domain" +config_dnsmasq dhcp-option=15,"cloudnine.internal" +config_dnsmasq " +# Send microsoft-specific option to tell windows to release the DHCP lease +# when it shuts down. Note the \"i\" flag, to tell dnsmasq to send the +# value as a four-byte integer - that's what microsoft wants. See +# http://technet2.microsoft.com/WindowsServer/en/library/a70f1bb7-d2d4-49f0-96d6-4b7414ecfaae1033.mspx?mfr=true" +config_dnsmasq dhcp-option=vendor:MSFT,2,1i +config_dnsmasq " +# The DHCP server needs somewhere on disk to keep its lease database. +# This defaults to a sane location, but if you want to change it, use +# the line below. +#dhcp-leasefile=/var/lib/misc/dnsmasq.leases" +config_dnsmasq leasefile-ro +config_dnsmasq " +# For debugging purposes, log each DNS query as it passes through +# dnsmasq." +config_dnsmasq log-queries +config_dnsmasq log-facility=/var/log/dnsmasq.log +config_dnsmasq " +# Run an executable when a DHCP lease is created or destroyed. +# The arguments sent to the script are \"add\" or \"del\", +# then the MAC address, the IP address and finally the hostname +# if there is one." +config_dnsmasq dhcp-script=/usr/bin/echoer.sh +config_dnsmasq dhcp-scriptuser=root +config_dnsmasq dhcp-authoritative +config_dnsmasq " +# Ignore any bootp and pxe boot request +" +config_dnsmasq dhcp-ignore=bootp +config_dnsmasq dhcp-vendorclass=pxestuff,PXEClient +config_dnsmasq dhcp-ignore=pxestuff + +[ -f /usr/sbin/setenforce ] && /usr/sbin/setenforce 0 +[ $? -ne 0 ] && exit_with_error "Can not set seLinux to passive mode" + +# Open DHCP ports in iptable +chkconfig --list iptables | grep "on" +if [ $? -eq 0 ]; then + iptables-save | grep 'A INPUT -p udp -m udp --dport 67 -j ACCEPT' >/dev/null + if [ $? -ne 0 ]; then + iptables -I INPUT 1 -p udp --dport 67 -j ACCEPT + if [ $? -ne 0 ]; then + exit_with_error "iptables -I INPUT 1 -p udp --dport 67 -j ACCEPT failed" + fi + echo "iptables:Open udp port 67 for DHCP" + fi + + iptables-save | grep 'A INPUT -p tcp -m tcp --dport 67 -j ACCEPT' >/dev/null + if [ $? -ne 0 ]; then + iptables -I INPUT 1 -p tcp --dport 67 -j ACCEPT + if [ $? -ne 0 ]; then + exit_with_error "iptables -I INPUT 1 -p tcp --dport 67 -j ACCEPT failed" + fi + echo "iptables:Open tcp port 67 for DHCP" + fi + + iptables-save | grep 'A INPUT -p udp -m udp --dport 53 -j ACCEPT' >/dev/null + if [ $? -ne 0 ]; then + iptables -I INPUT 1 -p udp --dport 53 -j ACCEPT + if [ $? -ne 0 ]; then + exit_with_error "iptables -I INPUT 1 -p udp --dport 53 -j ACCEPT failed" + fi + echo "iptables:Open udp port 53 for DHCP" + fi + + iptables-save | grep 'A INPUT -p tcp -m tcp --dport 53 -j ACCEPT' >/dev/null + if [ $? -ne 0 ]; then + iptables -I INPUT 1 -p tcp --dport 53 -j ACCEPT + if [ $? -ne 0 ]; then + exit_with_error "iptables -I INPUT 1 -p tcp --dport 53 -j ACCEPT failed" + fi + echo "iptables:Open tcp port 53 for DHCP" + fi + + service iptables save + if [ $? -ne 0 ]; then + exit_with_error "service iptables save failed" + fi +fi + +# Set up upstream DNS +[ -f /etc/dnsmasq-resolv.conf ] || echo nameserver $dns > /etc/dnsmasq-resolv.conf +[ $? -ne 0 ] && exit_with_error "cannot create /etc/dnsmasq-resolv.conf" + +service dnsmasq restart +[ $? -ne 0 ] && exit_with_error "service dnsmasq restart failed" + +exit 0 diff --git a/cosmic-core/scripts/network/juniper/access-profile-add.xml b/cosmic-core/scripts/network/juniper/access-profile-add.xml new file mode 100644 index 0000000000..26bfcf279c --- /dev/null +++ b/cosmic-core/scripts/network/juniper/access-profile-add.xml @@ -0,0 +1,38 @@ + + + + + + +%access-profile-name% + +%username% + +%password% + + + +%address-pool-name% + + + + + + diff --git a/cosmic-core/scripts/network/juniper/access-profile-getall.xml b/cosmic-core/scripts/network/juniper/access-profile-getall.xml new file mode 100644 index 0000000000..35ea81bbbb --- /dev/null +++ b/cosmic-core/scripts/network/juniper/access-profile-getall.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/access-profile-getone.xml b/cosmic-core/scripts/network/juniper/access-profile-getone.xml new file mode 100644 index 0000000000..300686252b --- /dev/null +++ b/cosmic-core/scripts/network/juniper/access-profile-getone.xml @@ -0,0 +1,29 @@ + + + + + + +%access-profile-name% + + + + + diff --git a/cosmic-core/scripts/network/juniper/address-book-entry-add.xml b/cosmic-core/scripts/network/juniper/address-book-entry-add.xml new file mode 100644 index 0000000000..ecf24c3d9a --- /dev/null +++ b/cosmic-core/scripts/network/juniper/address-book-entry-add.xml @@ -0,0 +1,37 @@ + + + + + + + +%zone% + +
    +%entry-name% +%ip% +
    +
    +
    +
    +
    +
    +
    +
    diff --git a/cosmic-core/scripts/network/juniper/address-book-entry-getall.xml b/cosmic-core/scripts/network/juniper/address-book-entry-getall.xml new file mode 100644 index 0000000000..508d265e07 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/address-book-entry-getall.xml @@ -0,0 +1,33 @@ + + + + + + + +%zone% + + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/address-book-entry-getone.xml b/cosmic-core/scripts/network/juniper/address-book-entry-getone.xml new file mode 100644 index 0000000000..276a085211 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/address-book-entry-getone.xml @@ -0,0 +1,36 @@ + + + + + + + +%zone% + +
    +%entry-name% +
    +
    +
    +
    +
    +
    +
    +
    diff --git a/cosmic-core/scripts/network/juniper/address-pool-add.xml b/cosmic-core/scripts/network/juniper/address-pool-add.xml new file mode 100644 index 0000000000..5304d20903 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/address-pool-add.xml @@ -0,0 +1,44 @@ + + + + + + + +%address-pool-name% + + +%guest-network-cidr% + +%address-range-name% +%low-address% +%high-address% + + +%primary-dns-address% + + + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/address-pool-getall.xml b/cosmic-core/scripts/network/juniper/address-pool-getall.xml new file mode 100644 index 0000000000..0da2b449eb --- /dev/null +++ b/cosmic-core/scripts/network/juniper/address-pool-getall.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/address-pool-getone.xml b/cosmic-core/scripts/network/juniper/address-pool-getone.xml new file mode 100644 index 0000000000..ab3fd17b79 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/address-pool-getone.xml @@ -0,0 +1,31 @@ + + + + + + + +%address-pool-name% + + + + + + diff --git a/cosmic-core/scripts/network/juniper/application-add.xml b/cosmic-core/scripts/network/juniper/application-add.xml new file mode 100644 index 0000000000..177329a035 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/application-add.xml @@ -0,0 +1,31 @@ + + + + + + +%name% +%protocol% +%dest-port-icmp% + + + + + diff --git a/cosmic-core/scripts/network/juniper/application-getone.xml b/cosmic-core/scripts/network/juniper/application-getone.xml new file mode 100644 index 0000000000..4f6b5546a5 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/application-getone.xml @@ -0,0 +1,29 @@ + + + + + + +%name% + + + + + diff --git a/cosmic-core/scripts/network/juniper/close-configuration.xml b/cosmic-core/scripts/network/juniper/close-configuration.xml new file mode 100644 index 0000000000..506e389d7b --- /dev/null +++ b/cosmic-core/scripts/network/juniper/close-configuration.xml @@ -0,0 +1,21 @@ + + + + diff --git a/cosmic-core/scripts/network/juniper/commit.xml b/cosmic-core/scripts/network/juniper/commit.xml new file mode 100644 index 0000000000..483d68d371 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/commit.xml @@ -0,0 +1,22 @@ + + + + + diff --git a/cosmic-core/scripts/network/juniper/dest-nat-pool-add.xml b/cosmic-core/scripts/network/juniper/dest-nat-pool-add.xml new file mode 100644 index 0000000000..84dcab096f --- /dev/null +++ b/cosmic-core/scripts/network/juniper/dest-nat-pool-add.xml @@ -0,0 +1,37 @@ + + + + + + + + +%pool-name% +
    +%private-address% +%dest-port% +
    +
    +
    +
    +
    +
    +
    +
    diff --git a/cosmic-core/scripts/network/juniper/dest-nat-pool-getall.xml b/cosmic-core/scripts/network/juniper/dest-nat-pool-getall.xml new file mode 100644 index 0000000000..1453bb4ccd --- /dev/null +++ b/cosmic-core/scripts/network/juniper/dest-nat-pool-getall.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/dest-nat-pool-getone.xml b/cosmic-core/scripts/network/juniper/dest-nat-pool-getone.xml new file mode 100644 index 0000000000..b5464e7cb2 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/dest-nat-pool-getone.xml @@ -0,0 +1,33 @@ + + + + + + + + +%pool-name% + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/dest-nat-rule-add.xml b/cosmic-core/scripts/network/juniper/dest-nat-rule-add.xml new file mode 100644 index 0000000000..559b86c624 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/dest-nat-rule-add.xml @@ -0,0 +1,56 @@ + + + + + + + + +%rule-set% +%from-zone% + +%rule-name% + + +%public-address% + + +%src-port% + + + + + +%pool-name% + + + + + + + + + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/dest-nat-rule-getall.xml b/cosmic-core/scripts/network/juniper/dest-nat-rule-getall.xml new file mode 100644 index 0000000000..a6c9cb276f --- /dev/null +++ b/cosmic-core/scripts/network/juniper/dest-nat-rule-getall.xml @@ -0,0 +1,33 @@ + + + + + + + + +%rule-set% + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/dest-nat-rule-getone.xml b/cosmic-core/scripts/network/juniper/dest-nat-rule-getone.xml new file mode 100644 index 0000000000..992dc0edf7 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/dest-nat-rule-getone.xml @@ -0,0 +1,38 @@ + + + + + + + + +%from-zone% + +%rule-name% + + + + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/dynamic-vpn-client-add.xml b/cosmic-core/scripts/network/juniper/dynamic-vpn-client-add.xml new file mode 100644 index 0000000000..16c0806d83 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/dynamic-vpn-client-add.xml @@ -0,0 +1,47 @@ + + + + + + + +%client-name% + +%guest-network-cidr% + + +0.0.0.0/0 + + +0.0.0.0/32 + + +1.1.1.1/24 + +%ipsec-vpn-name% + +%username% + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/dynamic-vpn-client-getall.xml b/cosmic-core/scripts/network/juniper/dynamic-vpn-client-getall.xml new file mode 100644 index 0000000000..86253edb53 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/dynamic-vpn-client-getall.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/dynamic-vpn-client-getone.xml b/cosmic-core/scripts/network/juniper/dynamic-vpn-client-getone.xml new file mode 100644 index 0000000000..e4564230bb --- /dev/null +++ b/cosmic-core/scripts/network/juniper/dynamic-vpn-client-getone.xml @@ -0,0 +1,31 @@ + + + + + + + +%client-name% + + + + + + diff --git a/cosmic-core/scripts/network/juniper/filter-getone.xml b/cosmic-core/scripts/network/juniper/filter-getone.xml new file mode 100644 index 0000000000..1ec2b141cb --- /dev/null +++ b/cosmic-core/scripts/network/juniper/filter-getone.xml @@ -0,0 +1,29 @@ + + + + + + +%filter-name% + + + + + diff --git a/cosmic-core/scripts/network/juniper/filter-term-getone.xml b/cosmic-core/scripts/network/juniper/filter-term-getone.xml new file mode 100644 index 0000000000..dc659afc17 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/filter-term-getone.xml @@ -0,0 +1,32 @@ + + + + + + +%filter-name% + +%term-name% + + + + + + diff --git a/cosmic-core/scripts/network/juniper/firewall-filter-bytes-getall.xml b/cosmic-core/scripts/network/juniper/firewall-filter-bytes-getall.xml new file mode 100644 index 0000000000..9384daf48d --- /dev/null +++ b/cosmic-core/scripts/network/juniper/firewall-filter-bytes-getall.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/cosmic-core/scripts/network/juniper/firewall-filter-term-add.xml b/cosmic-core/scripts/network/juniper/firewall-filter-term-add.xml new file mode 100644 index 0000000000..e7f3a63f8e --- /dev/null +++ b/cosmic-core/scripts/network/juniper/firewall-filter-term-add.xml @@ -0,0 +1,43 @@ + + + + + + +%filter-name% + +%term-name% + +%source-address-entries% + +%dest-ip-address% + +%protocol-options% + + +%count-name% + + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/firewall-filter-term-getone.xml b/cosmic-core/scripts/network/juniper/firewall-filter-term-getone.xml new file mode 100644 index 0000000000..2c7e10dd35 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/firewall-filter-term-getone.xml @@ -0,0 +1,32 @@ + + + + + + +%filter-name% + +%term-name% + + + + + + diff --git a/cosmic-core/scripts/network/juniper/guest-vlan-filter-term-add.xml b/cosmic-core/scripts/network/juniper/guest-vlan-filter-term-add.xml new file mode 100644 index 0000000000..d0f52a0be4 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/guest-vlan-filter-term-add.xml @@ -0,0 +1,36 @@ + + + + + + +%filter-name% + +%term-name% + +%term-name% + + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/ike-gateway-add.xml b/cosmic-core/scripts/network/juniper/ike-gateway-add.xml new file mode 100644 index 0000000000..3fa32da91f --- /dev/null +++ b/cosmic-core/scripts/network/juniper/ike-gateway-add.xml @@ -0,0 +1,39 @@ + + + + + + + +%gateway-name% +%ike-policy-name% + +%ike-gateway-hostname% + +%public-interface-name% + +%access-profile-name% + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/ike-gateway-getall.xml b/cosmic-core/scripts/network/juniper/ike-gateway-getall.xml new file mode 100644 index 0000000000..f3a93738a6 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/ike-gateway-getall.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/ike-gateway-getone.xml b/cosmic-core/scripts/network/juniper/ike-gateway-getone.xml new file mode 100644 index 0000000000..75a36c78ad --- /dev/null +++ b/cosmic-core/scripts/network/juniper/ike-gateway-getone.xml @@ -0,0 +1,31 @@ + + + + + + + +%gateway-name% + + + + + + diff --git a/cosmic-core/scripts/network/juniper/ike-policy-add.xml b/cosmic-core/scripts/network/juniper/ike-policy-add.xml new file mode 100644 index 0000000000..b80cca3cfd --- /dev/null +++ b/cosmic-core/scripts/network/juniper/ike-policy-add.xml @@ -0,0 +1,36 @@ + + + + + + + +%policy-name% +aggressive +%proposal-name% + +%pre-shared-key% + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/ike-policy-getall.xml b/cosmic-core/scripts/network/juniper/ike-policy-getall.xml new file mode 100644 index 0000000000..611230b8c1 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/ike-policy-getall.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/ike-policy-getone.xml b/cosmic-core/scripts/network/juniper/ike-policy-getone.xml new file mode 100644 index 0000000000..604eefe35e --- /dev/null +++ b/cosmic-core/scripts/network/juniper/ike-policy-getone.xml @@ -0,0 +1,31 @@ + + + + + + + +%policy-name% + + + + + + diff --git a/cosmic-core/scripts/network/juniper/ipsec-vpn-add.xml b/cosmic-core/scripts/network/juniper/ipsec-vpn-add.xml new file mode 100644 index 0000000000..9fc146cfc9 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/ipsec-vpn-add.xml @@ -0,0 +1,36 @@ + + + + + + + +%ipsec-vpn-name% + +%ike-gateway% +%ipsec-policy-name% + +on-traffic + + + + + + diff --git a/cosmic-core/scripts/network/juniper/ipsec-vpn-getall.xml b/cosmic-core/scripts/network/juniper/ipsec-vpn-getall.xml new file mode 100644 index 0000000000..54be5cef42 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/ipsec-vpn-getall.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/ipsec-vpn-getone.xml b/cosmic-core/scripts/network/juniper/ipsec-vpn-getone.xml new file mode 100644 index 0000000000..18fdd0d130 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/ipsec-vpn-getone.xml @@ -0,0 +1,31 @@ + + + + + + + +%ipsec-vpn-name% + + + + + + diff --git a/cosmic-core/scripts/network/juniper/login.xml b/cosmic-core/scripts/network/juniper/login.xml new file mode 100644 index 0000000000..cec595ffd3 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/login.xml @@ -0,0 +1,31 @@ + + + + + + +%username% + + +%password% + + + + diff --git a/cosmic-core/scripts/network/juniper/open-configuration.xml b/cosmic-core/scripts/network/juniper/open-configuration.xml new file mode 100644 index 0000000000..57b7c66f06 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/open-configuration.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/cosmic-core/scripts/network/juniper/private-interface-add.xml b/cosmic-core/scripts/network/juniper/private-interface-add.xml new file mode 100644 index 0000000000..f0ccb6e0e7 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/private-interface-add.xml @@ -0,0 +1,41 @@ + + + + + + +%private-interface-name% + + +%vlan-id% +%vlan-id% + + +
    +%private-interface-ip% +
    +
    +
    +
    +
    +
    +
    +
    +
    diff --git a/cosmic-core/scripts/network/juniper/private-interface-getall.xml b/cosmic-core/scripts/network/juniper/private-interface-getall.xml new file mode 100644 index 0000000000..d168e1f9fd --- /dev/null +++ b/cosmic-core/scripts/network/juniper/private-interface-getall.xml @@ -0,0 +1,29 @@ + + + + + + +%private-interface-name% + + + + + diff --git a/cosmic-core/scripts/network/juniper/private-interface-getone.xml b/cosmic-core/scripts/network/juniper/private-interface-getone.xml new file mode 100644 index 0000000000..474e671936 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/private-interface-getone.xml @@ -0,0 +1,33 @@ + + + + + + +%private-interface-name% + + +%vlan-id% + + + + + + diff --git a/cosmic-core/scripts/network/juniper/private-interface-with-filters-add.xml b/cosmic-core/scripts/network/juniper/private-interface-with-filters-add.xml new file mode 100644 index 0000000000..3ce8c55b24 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/private-interface-with-filters-add.xml @@ -0,0 +1,49 @@ + + + + + + +%private-interface-name% + + +%vlan-id% +%vlan-id% + + + + +%input-filter-name% + + +%output-filter-name% + + +
    +%private-interface-ip% +
    +
    +
    +
    +
    +
    +
    +
    +
    diff --git a/cosmic-core/scripts/network/juniper/proxy-arp-add.xml b/cosmic-core/scripts/network/juniper/proxy-arp-add.xml new file mode 100644 index 0000000000..ae6dee0f23 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/proxy-arp-add.xml @@ -0,0 +1,36 @@ + + + + + + + + +%public-interface-name% +
    +%public-ip-address% +
    +
    +
    +
    +
    +
    +
    +
    diff --git a/cosmic-core/scripts/network/juniper/proxy-arp-getall.xml b/cosmic-core/scripts/network/juniper/proxy-arp-getall.xml new file mode 100644 index 0000000000..3f23a22cd5 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/proxy-arp-getall.xml @@ -0,0 +1,31 @@ + + + + + + + +%interface-name% + + + + + + diff --git a/cosmic-core/scripts/network/juniper/proxy-arp-getone.xml b/cosmic-core/scripts/network/juniper/proxy-arp-getone.xml new file mode 100644 index 0000000000..e43dc0bc5e --- /dev/null +++ b/cosmic-core/scripts/network/juniper/proxy-arp-getone.xml @@ -0,0 +1,36 @@ + + + + + + + + +%public-interface-name% +
    +%public-ip-address% +
    +
    +
    +
    +
    +
    +
    +
    diff --git a/cosmic-core/scripts/network/juniper/public-ip-filter-term-add.xml b/cosmic-core/scripts/network/juniper/public-ip-filter-term-add.xml new file mode 100644 index 0000000000..9aad4c2399 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/public-ip-filter-term-add.xml @@ -0,0 +1,41 @@ + + + + + + +%filter-name% + +%term-name% + +<%address-type%> +%ip-address% + + + +%term-name% + + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/rollback.xml b/cosmic-core/scripts/network/juniper/rollback.xml new file mode 100644 index 0000000000..6e69b00374 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/rollback.xml @@ -0,0 +1,21 @@ + + + + diff --git a/cosmic-core/scripts/network/juniper/security-policy-add.xml b/cosmic-core/scripts/network/juniper/security-policy-add.xml new file mode 100644 index 0000000000..2e5a7d0284 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/security-policy-add.xml @@ -0,0 +1,46 @@ + + + + + + + +%from-zone% +%to-zone% + +%policy-name% + +%src-address% +%dst-address% +%applications% + + +%action% +%tunnel% + + + + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/security-policy-getall.xml b/cosmic-core/scripts/network/juniper/security-policy-getall.xml new file mode 100644 index 0000000000..a3b608a02d --- /dev/null +++ b/cosmic-core/scripts/network/juniper/security-policy-getall.xml @@ -0,0 +1,32 @@ + + + + + + + +%from-zone% +%to-zone% + + + + + + diff --git a/cosmic-core/scripts/network/juniper/security-policy-getone.xml b/cosmic-core/scripts/network/juniper/security-policy-getone.xml new file mode 100644 index 0000000000..55b6612106 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/security-policy-getone.xml @@ -0,0 +1,35 @@ + + + + + + + +%from-zone% +%to-zone% + +%policy-name% + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/security-policy-group.xml b/cosmic-core/scripts/network/juniper/security-policy-group.xml new file mode 100644 index 0000000000..1d0dc8cba1 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/security-policy-group.xml @@ -0,0 +1,32 @@ + + + + + + + +%from-zone% +%to-zone% + + + + + + diff --git a/cosmic-core/scripts/network/juniper/security-policy-rename.xml b/cosmic-core/scripts/network/juniper/security-policy-rename.xml new file mode 100644 index 0000000000..002325e66d --- /dev/null +++ b/cosmic-core/scripts/network/juniper/security-policy-rename.xml @@ -0,0 +1,35 @@ + + + + + + + +%from-zone% +%to-zone% + +%policy-name% + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/src-nat-pool-add.xml b/cosmic-core/scripts/network/juniper/src-nat-pool-add.xml new file mode 100644 index 0000000000..1ae75ee18f --- /dev/null +++ b/cosmic-core/scripts/network/juniper/src-nat-pool-add.xml @@ -0,0 +1,34 @@ + + + + + + + + +%pool-name% +
    %address%
    +
    + +
    +
    +
    +
    +
    diff --git a/cosmic-core/scripts/network/juniper/src-nat-pool-getone.xml b/cosmic-core/scripts/network/juniper/src-nat-pool-getone.xml new file mode 100644 index 0000000000..02e45ef96e --- /dev/null +++ b/cosmic-core/scripts/network/juniper/src-nat-pool-getone.xml @@ -0,0 +1,33 @@ + + + + + + + + +%pool-name% + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/src-nat-rule-add.xml b/cosmic-core/scripts/network/juniper/src-nat-rule-add.xml new file mode 100644 index 0000000000..4093af7987 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/src-nat-rule-add.xml @@ -0,0 +1,48 @@ + + + + + + + + +%rule-set% +%from-zone% +%to-zone% + +%rule-name% + +%private-subnet% + + + +%pool-name% + + + + + + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/src-nat-rule-getall.xml b/cosmic-core/scripts/network/juniper/src-nat-rule-getall.xml new file mode 100644 index 0000000000..e04378bd5a --- /dev/null +++ b/cosmic-core/scripts/network/juniper/src-nat-rule-getall.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/src-nat-rule-getone.xml b/cosmic-core/scripts/network/juniper/src-nat-rule-getone.xml new file mode 100644 index 0000000000..999969b419 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/src-nat-rule-getone.xml @@ -0,0 +1,38 @@ + + + + + + + + +%from-zone% + +%rule-name% + + + + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/static-nat-rule-add.xml b/cosmic-core/scripts/network/juniper/static-nat-rule-add.xml new file mode 100644 index 0000000000..8316e631f4 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/static-nat-rule-add.xml @@ -0,0 +1,49 @@ + + + + + + + + +%rule-set% +%from-zone% + +%rule-name% + + +%original-ip% + + + + +%translated-ip% + + + + + + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/static-nat-rule-getall.xml b/cosmic-core/scripts/network/juniper/static-nat-rule-getall.xml new file mode 100644 index 0000000000..a5d0f0a289 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/static-nat-rule-getall.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/static-nat-rule-getone.xml b/cosmic-core/scripts/network/juniper/static-nat-rule-getone.xml new file mode 100644 index 0000000000..bbec6c3c78 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/static-nat-rule-getone.xml @@ -0,0 +1,38 @@ + + + + + + + + +%rule-set% + +%rule-name% + + + + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/template-entry.xml b/cosmic-core/scripts/network/juniper/template-entry.xml new file mode 100644 index 0000000000..ab92d6d7d7 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/template-entry.xml @@ -0,0 +1,21 @@ + +<%name%> +%value% + diff --git a/cosmic-core/scripts/network/juniper/test.xml b/cosmic-core/scripts/network/juniper/test.xml new file mode 100644 index 0000000000..16bbd79a18 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/test.xml @@ -0,0 +1,34 @@ + + + + + + + + +trust + + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/zone-interface-add.xml b/cosmic-core/scripts/network/juniper/zone-interface-add.xml new file mode 100644 index 0000000000..4b93ba3404 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/zone-interface-add.xml @@ -0,0 +1,35 @@ + + + + + + + +%private-zone-name% + +%zone-interface-name% + + + + + + + + diff --git a/cosmic-core/scripts/network/juniper/zone-interface-getone.xml b/cosmic-core/scripts/network/juniper/zone-interface-getone.xml new file mode 100644 index 0000000000..b0ead3cd85 --- /dev/null +++ b/cosmic-core/scripts/network/juniper/zone-interface-getone.xml @@ -0,0 +1,35 @@ + + + + + + + +%private-zone-name% + +%zone-interface-name% + + + + + + + + diff --git a/cosmic-core/scripts/network/ping/baremetal_user_data.py b/cosmic-core/scripts/network/ping/baremetal_user_data.py new file mode 100755 index 0000000000..a8ce32cb3b --- /dev/null +++ b/cosmic-core/scripts/network/ping/baremetal_user_data.py @@ -0,0 +1,104 @@ +# 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. + +''' +Created on Jul 2, 2012 + +@author: frank +''' +import sys +import os +import os.path +import base64 + +HTML_ROOT = "/var/www/html/" + +def writeIfNotHere(fileName, texts): + if not os.path.exists(fileName): + entries = [] + else: + f = open(fileName, 'r') + entries = f.readlines() + f.close() + + texts = [ "%s\n" % t for t in texts ] + need = False + for t in texts: + if not t in entries: + entries.append(t) + need = True + + if need: + f = open(fileName, 'w') + f.write(''.join(entries)) + f.close() + +def createRedirectEntry(vmIp, folder, filename): + entry = "RewriteRule ^%s$ ../%s/%%{REMOTE_ADDR}/%s [L,NC,QSA]" % (filename, folder, filename) + htaccessFolder="/var/www/html/latest" + htaccessFile=os.path.join(htaccessFolder, ".htaccess") + if not os.path.exists(htaccessFolder): + os.makedirs(htaccessFolder) + writeIfNotHere(htaccessFile, ["Options +FollowSymLinks", "RewriteEngine On", entry]) + + htaccessFolder = os.path.join("/var/www/html/", folder, vmIp) + if not os.path.exists(htaccessFolder): + os.makedirs(htaccessFolder) + htaccessFile=os.path.join(htaccessFolder, ".htaccess") + entry="Options -Indexes\nOrder Deny,Allow\nDeny from all\nAllow from %s" % vmIp + f = open(htaccessFile, 'w') + f.write(entry) + f.close() + + if folder in ['metadata', 'meta-data']: + entry1="RewriteRule ^meta-data/(.+)$ ../%s/%%{REMOTE_ADDR}/$1 [L,NC,QSA]" % folder + htaccessFolder="/var/www/html/latest" + htaccessFile=os.path.join(htaccessFolder, ".htaccess") + entry2="RewriteRule ^meta-data/$ ../%s/%%{REMOTE_ADDR}/meta-data [L,NC,QSA]" % folder + writeIfNotHere(htaccessFile, [entry1, entry2]) + + +def addUserData(vmIp, folder, fileName, contents): + + baseFolder = os.path.join(HTML_ROOT, folder, vmIp) + if not os.path.exists(baseFolder): + os.makedirs(baseFolder) + + createRedirectEntry(vmIp, folder, fileName) + + datafileName = os.path.join(HTML_ROOT, folder, vmIp, fileName) + metaManifest = os.path.join(HTML_ROOT, folder, vmIp, "meta-data") + if folder == "userdata": + if contents != "none": + contents = base64.urlsafe_b64decode(contents) + else: + contents = "" + + f = open(datafileName, 'w') + f.write(contents) + f.close() + + if folder == "metadata" or folder == "meta-data": + writeIfNotHere(metaManifest, fileName) + +if __name__ == '__main__': + string = sys.argv[1] + allEntires = string.split(";") + for entry in allEntires: + (vmIp, folder, fileName, contents) = entry.split(',', 3) + addUserData(vmIp, folder, fileName, contents) + sys.exit(0) diff --git a/cosmic-core/scripts/network/ping/prepare_kickstart_kernel_initrd.py b/cosmic-core/scripts/network/ping/prepare_kickstart_kernel_initrd.py new file mode 100755 index 0000000000..ff618480e6 --- /dev/null +++ b/cosmic-core/scripts/network/ping/prepare_kickstart_kernel_initrd.py @@ -0,0 +1,75 @@ +#!/usr/bin/python +# 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. + +import sys +import tempfile +import os.path +import os + +kernel = None +initrd = None +copy_to = None + +def cmd(cmdstr, err=True): + print cmdstr + if os.system(cmdstr) != 0 and err: + raise Exception("Failed to run shell command: %s" % cmdstr) + +def prepare(): + global kernel, initrd, copy_to + try: + k = os.path.join(copy_to, "vmlinuz") + i = os.path.join(copy_to, "initrd.img") + if os.path.exists(k) and os.path.exists(i): + print "Having template(%s) prepared already, skip copying" % copy_to + return 0 + else: + if not os.path.exists(copy_to): + os.makedirs(copy_to) + + + def copy_from_nfs(src, dst): + mnt_path = tempfile.mkdtemp() + try: + nfs_path = os.path.dirname(src) + filename = os.path.basename(src) + t = os.path.join(mnt_path, filename) + mnt = "mount %s %s" % (nfs_path, mnt_path) + cmd(mnt) + cp = "cp -f %s %s" % (t, dst) + cmd(cp) + finally: + umnt = "umount %s" % mnt_path + cmd(umnt, False) + rm = "rm -r %s" % mnt_path + cmd(rm, False) + + copy_from_nfs(kernel, copy_to) + copy_from_nfs(initrd, copy_to) + except Exception, e: + print e + return 1 + +if __name__ == "__main__": + if len(sys.argv) < 4: + print "Usage: prepare_kickstart_kerneal_initrd.py path_to_kernel path_to_initrd path_kernel_initrd_copy_to" + sys.exit(1) + + (kernel, initrd, copy_to) = sys.argv[1:] + sys.exit(prepare()) + diff --git a/cosmic-core/scripts/storage/checkchildren.sh b/cosmic-core/scripts/storage/checkchildren.sh new file mode 100755 index 0000000000..55122d4f3a --- /dev/null +++ b/cosmic-core/scripts/storage/checkchildren.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env 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. + + +# $Id: checkchildren.sh 9132 2010-06-04 20:17:43Z manuel $ $HeadURL: svn://svn.lab.vmops.com/repos/vmdev/java/scripts/storage/checkchildren.sh $ +# checkchdilren.sh -- Does this path has children? + +usage() { + printf "Usage: %s path \n" $(basename $0) >&2 +} + +if [ $# -ne 1 ] +then + usage + exit 1 +fi + +#set -x + +fs=$1 +if [ "${fs:0:1}" != "/" ] +then + fs="/"$fs +fi + +if [ -d $fs ] +then + if [ `ls -l $fs | grep -v total | wc -l | awk '{print $1}'` -eq 0 ] + then + exit 0 + else + exit 1 + fi +fi + +exit 0 diff --git a/cosmic-core/scripts/storage/installIso.sh b/cosmic-core/scripts/storage/installIso.sh new file mode 100755 index 0000000000..fd301bc5ca --- /dev/null +++ b/cosmic-core/scripts/storage/installIso.sh @@ -0,0 +1,130 @@ +#!/usr/bin/env 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. + + +# $Id: installIso.sh 9132 2010-06-04 20:17:43Z manuel $ $HeadURL: svn://svn.lab.vmops.com/repos/vmdev/java/scripts/storage/installIso.sh $ +# installIso.sh -- install an iso + +usage() { + printf "Usage: %s: -t -f -c [-u]\n" $(basename $0) >&2 +} + + +#set -x + +verify_cksum() { + echo "$1 $2" | md5sum -c --status + if [ $? -gt 0 ] + then + printf "Checksum failed, not proceeding with install\n" + exit 3 + fi +} + +install_file() { + local isofs=$1 + local isofile=$2 + local cleanup=$3 + local tmpltname=$4 + + mv $isofile /$isofs/$tmpltname + + if [ $? -gt 0 ] + then + printf "Move operation failed, iso $isofile not installed\n" + exit 4 + fi + + #create symbolic link for iso file + file=$tmpltname + isofs=$isofs/$file + mp=${isofs%/iso/*} + mp=/$mp/iso + path=${isofs:${#mp}} + pushd $mp + ln -s $path $file + popd + + +} + + +tflag= +fflag= +cleanup=false +cflag= +tmpltname= + +while getopts 'ut:f:n:c:' OPTION +do + case $OPTION in + t) tflag=1 + isofs="$OPTARG" + ;; + f) fflag=1 + isofile="$OPTARG" + ;; + c) cflag=1 + cksum="$OPTARG" + ;; + u) cleanup="true" + ;; + n) tmpltname="$OPTARG" + ;; + ?) usage + exit 2 + ;; + esac +done + +if [ "$tflag$fflag" != "11" ] +then + usage + exit 2 +fi + +if [ -n "$cksum" ] +then + verify_cksum $cksum $isofile +fi + +if [ ${isofs:0:1} == / ] +then + isofs=${isofs:1} +fi + +if [ ! -d /$isofs ] +then + mkdir -p /$isofs + if [ $? -gt 0 ] + then + printf "Failed to create iso fs $isofs\n" >&2 + exit 1 + fi +fi + +install_file $isofs $isofile $cleanup $tmpltname + +isofilename=${isofile##*/} +today=$(date '+%m_%d_%Y') + +echo "filename=$tmpltname" > /$isofs/template.properties +echo "snapshot.name=$today" >> /$isofs/template.properties + + +exit 0 diff --git a/cosmic-core/scripts/storage/qcow2/create_private_template.sh b/cosmic-core/scripts/storage/qcow2/create_private_template.sh new file mode 100755 index 0000000000..8e9e26c410 --- /dev/null +++ b/cosmic-core/scripts/storage/qcow2/create_private_template.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env 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. + + + +# $Id: create_private_template.sh 9804 2010-06-22 18:36:49Z alex $ $HeadURL: svn://svn.lab.vmops.com/repos/vmdev/java/scripts/storage/qcow2/create_private_template.sh $ +# create_private_template.sh -- create a private template from a snapshot +# @VERSION@ + +usage() { + printf "Usage: %s: -p -n

    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 literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/images/bg-system-chart-lines.png b/cosmic-client/cosmic-ui/images/bg-system-chart-lines.png new file mode 100644 index 0000000000000000000000000000000000000000..e551e48ffcee5741d23c4e0d485bbf6ff88516fd GIT binary patch literal 1837 zcmdT_ZD<>19KTYHVY+TZ{m}a5yfXTs>%AnoOYUge#>>TJEVZW5MW@8;IFSf-NTXYC^`%p%OIrOSIU~p7W;}A7lQabvEgQU`=n#z{-Ms}}l!k$c4uQ0+ z`mx(ruVJX1^kXOJ2pQ3Nkdcp$8DMa%KPHV0OKcMB?LoT>93)VHDWU}>tLC_ZAFJ|m zaBXc97+N(khy7ShR6H_(@|poq8YjCXlA%z>gHs-o^{_`!7fF!>qDLaz#A>ddD3~&$AnF8#lZ2vJaaFBzW)$3)(a<^< z8`S|31vzcRkYGJh?KT+ZZg)qPAymUXXUMQ9Vm7ErBMMN>aL|vz6Fey=ImQ*B1=8iE zD4!sZ@ExG}0Le3NYdtLgmrt_5FHX2p5X%&C6KJoM+!`kr$SyqpxlCu4XZg* zR3%{hm7(7{mfjW1@dgl0&4_7Q*6x6TjAm-NjHaXfAdORKM3iLJ+OY~$i#G@i`7}s| z3{63+8Rp~$3cLX!M7u*I%ktI8Suf@C1sEUYh9QJl9;;)M|0Qb#6hm0;v7=q=E!czB za=ZEA$M*FBD!elWyc`p!KZ(OTcQPCd#0pElFZqv61fH0FzWoR1i&*)K!p?e~>YKO5c&Uk+fTMZH@zrC+ zR%=*?HG}a|plVP(tQj11KZu5h5271T+vo<=HhNFgwy+(lZE9QC4%Pomz1Ns8pNHAo zs+0?_GiOVSJ#SPxQhy{IY`SxN)lQA=#LnHniT>LA(;r=W^5Rr!y}Vvt>p!uQSX-HI zet|o)+_{3^Eq%9+pE=O7@$kuCe$-N#Nm!1)$oJD*%aunq`(|&gfBX4Un^<|SHTK(` zj@9`Of1R6Iyoe?4C5o%_3(osx-9v(Q)6V=~8s@FuW2er-QY9`pF5jt?7Y{T!ke_ZI V_OyLQd}{r`!a{$r{LE`_{|znLW;y@> literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/images/bg-system-network-traffic.png b/cosmic-client/cosmic-ui/images/bg-system-network-traffic.png new file mode 100644 index 0000000000000000000000000000000000000000..9aa99ae846d0d8dff4e6bc4c5adaf6d30500a000 GIT binary patch literal 13791 zcmd^mXINCrn(ji}f@Dl&5okda$vF!mw4^PdWDr53faIK%q#{V8%^AJ+iL``+}lGygxhLn7u_gi^um5caSRHR;cUkGKs&Ty9OZXm<^!Hf4k zlgr4Qrumu>h#MU29dz*?8_`RG0X#Irhv4UlfiJ)9kGmyW^i^z6w;mPsi_c`PBMaK` zYVe7|<#_J#c@w2cT_*qjys^Gv9L+B2LjZdUk>M8@G1wh4;Xpf{qM|n#Yw)Td9OrIA ze5gh-riPO?;FxqyDoO{(+ZU(CK2%U1-}?e2=^8AR3`yR>@kT~6t3nBu}I6I9biMwk!b6G5E!0{vK_({9kiuS`tNkZ%-p;m(@6=<1DZvYG23Qc?M$r7$5G zA8q`bHhAjl?3b^#!6|NB;5RsB5G~1G>k>u9?ZZp@eZ2*O5<)KmZ`Ti<+eot8+W3Qu zN%U{dRi8O#eDrAYXsSKqsU!rA+k18%v2heodyAa$wmkZ9b?LOJ9$|vR_ABFjQpqAH zVRlG;{V66kGVkA%4h~MC(F1qurL+ycse2rm)auu$A3oXj6g}A9{9ZKy^WoO@kv+Zh zy|!gTA&Isfan|?I$aIM8!6(Aw1G;X;dibMKjq4XC@13&=z8xL3&Yi^gLJGn9n|*PFbkP zSBU%Bfq#W8*IS?bdtyCAMbf3I0@S{DJ`i>o*>gpQTuO-Puq~t(3*vu2(~)6HIus%< z-N;$f8sJxaBW44cqgaPM`CUsX5Dk+DYn) z!R1?W8VVXp19GWXXkonMLuUof;`-jJWnm0Ul`mv2Bj2s3e8kNapdrV`)ORlAJO`Ou zpmeGP20W6MD;b<;GV(G!k8h z=0Ve-8P>E3kdGv7KJKVaTvPdGewS*BY>Id4%;QMzTlW*ARYp_atC91CXh~(gFILI9 z7m#|M*CAS{_4y^Zd{Wkjciq&c6sFr|IX~>n$VcB@R*L9Q?`ocv{Som4cf*N@*#F}5 z#z2#6R56rzlqOV8lsStnX`FFTU=6G4vb3ySwk0-_cy> zP)kcsP9IAryuW_GSuG?@Pt_y)&V3%$$+WXQ;TiYSk-yKY(x{rGiKGUko1|K+NqqFw zJbZCRvreH-S^DF>ggpJpiiRtMhExU4Yz!8}8U7AFeD%zEt%L54*UKXh>B?V;rKi0Y zlIFR~qh6r0{?=FmZ!*$mC)Qr+?98>4h;`4GV;7cLCs=8cc@;{z9_v`7hew2KWXNUQ z%aEO~~YZB7-8*)ZacLF*7m!F{n=uwVbusic^bA9>ep|TFIJA#T8b5T0)vh z8uzty3+`kgv#$1)XO*Xrr<5v%DP_d~AeWJ>p8aOkYU2IwGVPNuz!sOGJ z?7F3}$}o>IkJpD{c*FsNBpGyCbe?vFwikpS2#-a##V$#BeAx(}DcJl^|%vC@xF>s?L*$#V^25-CrX3X`lvmCszcYQ|E}Z9qRVp?eN=UF(zEfLOD0@@?95C zXEzre5-8&>lW1T!)@ZdQyctSX>X7Ku!=&x=}=OeARvG^1iKh z!!PAh8v8jm;c!WXKqbMqE4Rq5r(O!Wx^~{_YA}IIEnTe%BS$BHXW)Re&h^VmYjW2W zT@eeR6?`tRFFZ+XQ>DHvX>TeNh!sX-gySwnJ6^Hm789=IdX5NvVp@=YGA8&|i!_$Cyd5qG0? zv8CYK@Ed{H<@n>pbG7Hj5yeUV^6EF~G^Q&v&4uzsZCr*4CNKVW!~emVOwp2;#n&x+ zl`|IABeFd!nOzkveDpqld-k~q$6}uFKEafq$NWhDqOP(AhbF4XV9y^(BYyG4%j^ro z6cHD_2Cj`bPtgQef2)ofd0Uv4QD17*Zro)&J3V$YPMah=Jz_e7#EN)usE`=-pm1?> zOJM0r5^5jSHq`di_?NmyiwydAtU8xy+$y$s7kY=a zE4B~Co2rKIgK5WNEY5h}(sGh{r@y++W($OV=f) zrV`S3V_ue|k-q8(x$Jb4n4kFm>)3_~rl+@E(6ifTQQMb^g@+u2+%m`Uy6GM1Oz1ew zrJ~oO@5HD^?`uEOE>mt1@9di!b3Kq9rYDW%71eaPyROz`HZ=RJJIrLQJqY;@>EX$~ zJMwV&L$TY8VH?Vv)Oxv$%znH}=J?f&NQFq7&RU86TK@3VdG!+Y=p12v%Q^cw(W9_# z<;L>Z^62bR9gET>*UBO{W%Bo}WDN{G=9o;$ts{cW9*o$n*oBEqUI-IEUI zYUZhOh8t=dE>|@<^Kj{HJlJ3S>@?y;x15L~RYtpC@w#?wx|_GD+L5v!Mimxzd>S`$ z{lJjt<}m0tuNcayC_<3yRS5Ea4ne!e;P)&9IdDSI+(QTwj)WizYlQxnI}mh(0Dk+H zn)A@YsMp&O2an~|{snds<~xs0`P{ZvQF%n3DRUdYkn1)9fzOQznN&A<(#vhc#1uF2 zybNv>GZMTTntX4zO}%RSTRDN$jh@?*AuKO^XjN7i*QCwZKGjR^6jA&74#GsXLq^Z) zH-1Dnwk(9WhziokP#Wr*dB z4CosF20@aaS>bJp+#F48lLJiJcRiAN;Z-poKMkn@imgsV2MPkGld8I2z^fwPGksqu zUxZ&$cT3m51Uhl3M|S5}F5jL)`B<>Yo3--8AUt`=$fNb?n`5~^TnKujqCa~G-y@KZ zLFgdS{^rV?Hs^;DHI?LSZ4vTewE97-U})P9Xz+d-i$u%9^;8KPV9QxsY68)1;_X`> zh>&wAp^Cy($7t7pRiLjh>x&=$u?<31#@SHKEnObro|)TI&eTOmjt4=e5=m7mCJ0Nv zBvj6pT7RTM-Hf}Fp19&+t+{ehKRM{#ytnLa$B*ukkMYVAY@?8Zd~~33)or~oa-40$ z+iJPVLxFK>(adrD$grW4wl{8$kgDvQ2SVZc<~wFWAOJGWR`lDKq6MF{;200R$$(;r zfdu@6kNKVqBjCg!b}Dx8N%aQOl|- zUK>F%Vmun^Zm2z+1;GNbcS4xek>aI`^_utsTP5AJbW6ORfxzbZ?OU8rNe1C6%y{Rw z&l0hBH%9=GT$1|Ks|XW6Q&kY@WFq?aid{(bvO-cG0O9$tSXGU=i`aZD8v5;Cnr zC{Af@y9m|&SOLV{l5R2{R73LB-R~=12Jj`e&39)IBN6DvYLB{B)cbAD{@@8m79r1_hOympnAiQqv@Hq>WHd;>B$%y@s91ic-9=I6Zf8JLYBp)plQL;8>POOX62lOx`uIN zAj$`3;cw0RMztIKC5b^;%?`T99z5-sx}`?^zKIrtbni>lsdqiCj_=;?zLwJT7eBd0ir@~Rp7i&7a>DgKb$U<}p zz6Osug_-iYHwG;ggCNo^T$cB1EC_3n&mS@c3GA#*n+;+SV4+IYp69v*Dwmf^ASHQ! zWNPdF4$AV*UqgaM@02Oz9nI?h+JRCw&n1#M*fcP_zwC1rjC71E3D+4_E z3i39E6IH5L{3wKly#|LPz&6^=loVw-+weRvJJF||JmoO~#eIw7cks)~+!?ujVdxu% zE)Uq{z%F+3IS?*895$ly$x%u-5?~JB#x#Lu?y{5dk3_Jwhwi*p?KUeZjTp0kf^cGq zia-w#Rm!^Y?i{RG7HUNpZLTcd*n(k{uH3~r>Vs9*t+vL+@VJX`G*KYfl-3*!aUk#2 zFKM&9*K1MnXg%^V&M+SP&F%6q+S2K@?MPrk(D~rN?o@o#YN)}!gCjb#v>N%T+Qv25 z`S9)A?Rnmhqbuu26zhq{&g}ZtZn~aZJ?lkNlVLhBXv*`afb7rYHd}nPAquu0i>vSP zmgDD->~D$Ne_NhTl_t3`?)JS}!NWmG#QZ__e)n%e%f5A8o%hnkHj5`EHnW-)fO=m+ znDp}5<#d@H^-IV%(xhW3wXM&dNV?MRq@?Zf@iuBUyK5NteZfNSO_uVywQ?i5iAvZ( zn2s*#V9HBmh3y?bdw`cjv9ZeeEzDVwb;Z-|It&X#Sqb=~@ z{*Vp(-p4ghFuw0_G!$a?=>^6j%o_Bw0)Jg>|7cp&Q}jSp;lb0=g=FOZTZ79P2F<4?L_6MJLeCE8x&opcOx8c?}(`*D?h7Y zO0>Hwymu*6b6d@G?D)eA-hs`G%cFmbCQaE)dV#=I%`n*W=e-BJ0)5H(4&|SDzamKw zL{V-l{UqMo(clOX-=?I*aR$Y2^-bn@CVL{SXCcW;YhNRKXNLY#M0N>~LC;TxL2+Rz zAO<%}gq=kQy$&kgE_+wcwR-=l=LUT{RmWv#V43X%RwQ9MpS&hC4#T-5B02MWFpT73r(js}v}5wYP$V=J#m3mBy7&wk>r zqc9dZQbk>F>jyd|1M8HYyE_oXbLsH_ZX?31scuS&7+7G_fz9iH>==_$bIAZDKn$jV zIKkm32=!mdag8`|Ie`kSJxbE1`On$Qx&f>D3Q7jc<$tA&w!U!K9(=ZLxH7cw>aH1L zxM6NjVbWw_i^I1P@$XCdYeNC@kD$(12~GOp$i4K@(&h8%-ivNet%? z?y#T_H&Pk5QAQ{a!7!*FC>n!NP~*Cp-9vC(yxgt?dRX&B9H@a1^ctMeBe6yaR#}Z2 zfFHp6o51RBf8OoG%DpFt@bIfb1uGjuaP~2H0xlE;4-UlT9NOp=i;G5I09*wsiMKHy z7BknUBSr`C0Cm8$xi|DyKXfA$_^C0gvZP&4e4IE#lV-yyCj%s( zqD`5#TFQu=pn%=7NCv1-K(-!-@^vvwX`a=hZy6^ZR-U9lkJwU6hinuMrWI>I8iwhF z8p$A9;pt3I-5#&4T*_lg*;Jk&q_DTdAs$5okjA zC6?VX!0$4^f#27UVB0TO{G(#uXH=t*Xt_xUG6&5@Sm+1aD4$u7P{ zCzZK5{`aG~hr28V4wKR@vp&|3wU3W8Zqmo2hhUA7WEg13`3LvuRZnvGrv(^wy`YEp z_ed)klR7L(Fd_Hbzh+FceVyK~ifi5W;z=W6ZW>1Dgrj+qPeY)m_8ucT4jIe_&#k!~ zJ@8##M`i0qIk%KX+1CcV%!ptSs(p&de#aQI$&a4dk3FIA<&t`{AH*B!C?VF-09t(= zttZzp`8Bmp=~=RG7ZujUJ17P^~fvbv5j z+f={6>A-I#`nh7Frq`fS^aFRn=G&N@byP9;FTE|(x1?57ncFEMWnE=<$0XzL{P}`ZKNoG7VZ_a*;I8J%F~zxPLl9fU<9y;<4{xL6mALVA~oj^7Dr&i2eEcI&t{;K^Vll${DOb8P^ZVr=A%r7@@&BXJDo*A}17H|@_ z#tMt$$53V($+SAElDpRuH@WM|JBJ%8yMv@J9v$2osW$1(m*9xI+p6a} zJ3h7B`;r6jrQBL>69nnrfjYMOn1uwIh~fE*Y4KowbX_4oz3n@DMu2QdsMzEkH-A84c^r;&|MAh|2Wx;&C&vM0^1Z(1Srz zGzW|38?s%ERj5D>)x)ak|EN64R}}^o%uncv}BB7Zyek;)ZbVGR6VwI+k3+bWDkOKYxJf zUl)x-8tEZ=@8AYeU>1Uhyrn|bF1M>1HLz?5J-j703AXn z`4B7bCm#lxVXXZwkf~qteE_zMq4Yc|*N`pKmEG}S(<1h!x$$B31vsWuzP~$lkOg-Q zn2Zq-)2}s0}wpcT)RSXII2TGR~R@;qGF@nB|axQ|2U zY1CO9uo8P4{SX`16=6J>v&aX}^S|@>eQxgC(eYu0Lg^30#M;z61-oFaJ%(mF8o*fr9lpxYSH19SI&pvz^Qbv3lgM$COj^Qt;?r+jhuqy@{|48M}0?b;MT5S6selr;d zQ^=RW3|^QB50^=NLQU*q!3@Qq?JYG`)6_G=>Oc+|URBDPYY{ zTh8B8NN3WHT|DEe$GuFxhvE^Nl0NZT8hoO+c__NxNje>IJin|mTAa1jgUb_i&iF?*;HEC`ZAz2b>X1UlqF9R54CdZX)e3e$MJKVhqfU#K+h=kaa~L zX|xF@5#c6$2E7=_3AUZY0wOo0LKZ|v*EWyKyTJ|=73lkR;z~g;Z3(B8Au0QzB<;=r zU5+)Eg+IPvknEu-=B)1zB0fQt|H{()&w%3(lwl41OLoQ%Vg>zQYl*De?hkXDcAoMfc*DH`zI8zF+Abr$!LdY z?JpZyR<+`LGUG_h*QBnwIfPJ9o(&qs9vb|q@_r$RW_cHxnUzoVxgGZ1gbveSo#f?z zr^@~${zSa~Wc*e~`~2nO9yu%!TD$ePci>Do*r$JT%}=D9fX}bfb^=qsHsjZb{gaa? zRQ|N%Kg081qz#-9{H4khgTHqD-=j3Jr9OF9bRtSWEAQWX9>aS-6Y~>k-XBzowD`bv zQi1{Vn4pawg8o8y_KCe$Njb&$gs84&0w~l~0jqFzHc(h8`gIfzDrPb}Xj++7UiQ^*IUbm=UdtJO- z#(U`w5=P3R8D`{WvzjCBm`4wo#aD1~YR=+l{GhEZ*g{{z@J@1t<62~3Cpq!zy=~o) z*JNJV)e_22+^-K8TOLlfwPky4gyDhnjFg*5`#rbzkz>BFY>#%c6yu>nT-B@x#D3F- z?rCpX;@r7EdCUaoi0!xVXfN*_AqA@Eg9^?lo&k)iRIYiDE#WSktPq9p$r@I8o-F($ z;rUwjc~q#^UW9dgJlYMur}!J>omc;5Xn=(Uc{DbuFJQRWl;buUL?nDpiI9lo0guIW zOT|`Ej>ilQ${qdQX~fkoB@Nt9g)gjtV4`=mGVe#Y4E2 zvTfZ;PLO_B^XO=@WV3^#{l~WhyWqgqi$RY)g5_!{Z&Uo$sKOY1Cv!yH`0x-6+)@se zG^UvI*@(u5FFS!a*Ekaav)qjl76Emx)^sswJh+w8nh%VFa$it^ahVjHzzuvN0h&c*-_Yc$i+rh$c#%q4CeNNt z1ZMlVpcoj@JkoX=t2fISEAH%wa zhhs#RN)mTAdSEElh-Pa$BllQdXIgR-qb(5-k9ABu+TphMY!=^0lQbc&%G<6ru`I7r zNNo0~+Rde<7ayHz-6s! z-o|9gVIi~{)&bI0voC(Qq@uaA9Kw&kz1>SnkRZlfkYH3Xjz<6!Q^!z)@R&5ym$BMQ8BL9cgz%oAI=iIP<+%Rs*@? z0^v^x6i7SLbvNcw5KAbgV;`s_0~I^mJhiq6cQt?YY0TB4Tk0UYeCeA* zzpi?rz#>?+MwAHk-H2vZVfO^I_guhY-X$;t6FQ%q8^ zG2f#kWn>5@0rVw?zj`;x)~!@WF9tm@bY4D!-O@Qw-|KL*bZf|BK0;zRFvp`wU&qQK z*jrsWn%BM8zCbbn7pk!e*vTbj*}b*+d2On>z_jxNn{HVlz+8tk=@b+FuH50;x4TLC znYU2QF_Y_GwXL2$UD;&s^FIevR$^4t9J|Krp?&Z! zf5ns7`TS(4Fs(%N4SM2?CkIN2yP4TC%W}AwnD38>6p+zxvA~z$wi=e>uk;Td_2uZt zkByBf#EH6u=zrgv&uMRzI9lD<-tL!hut`!WMKM$Er{7;DH@Uqf$^>At5a67oGN{HL zp{#6cjiBrGXukQ$XmO~hbfuQiuIXyTLLY@*k@ZLCOHCq1%&~CwixOI4BZVu{@$S55jxSeX>)D{ zkNva1dFr39j!=e08HE);Fp+I!?L{llVX8iaL7a9{8=-TbJtBg6R}B#d>)zt5$+>Im zMpzXg9%FY}sv^U}JYA@p+(xTi91pxoM6@dPX4~Y4$0!9L)$@p01UOV5-G)b_yB;QAUMR{(i7Ej}sEPSh0>~*W}90w&ZK$i1r?H!eH7qLi*xiH4!=nE4o zG`G-?ECFpv#Xe{(AxoKZLy}`QmuCI6YnN^AJghEg#=-nKc@Fz(7MK-Yf~f;=OUqWL z%ZCgdSY81C`vc!*sHC2b+ysVUT=1PG=`6?$SXP_wj)03Z%#wc7%7#Jd)wU2wIp#L` z7I?9ZDQ|Lc20Pz|v=nni0G9A1F7|q}RdEwgv>2haeu7N-DDeIQS&Eq3wctP=?tSpi z7{1MIU?s5GShfWFrjD;*7Ml&G0S2a;y&|;1*aSZ6lD%NWn(+!;LtxCg!6+);1umI^ z1-0j#%!Qfqhq1DeiEnWn79TG z_`ux!0A4T#%zMYR6eyU5nh(M%(jL;IqqQ&M|3kazkxJ6U0PLoEni@AJXD(NhV>i|Y zC97i@FE5fZX;f6X&Q3T!2{z3yW zjY?KSYu4_k&*pKS6EB{O9ZbB7@$5WaC~p&NJ_~#cVkI%?3Zy(n!&9oA=C&+{FDVOs zuThBQWu>D#q9=XLSK{HuZ|%u-*e;5k5jb2MdgsKppW%eG>GK9RN+4?TDe=WCwbmTk zN2B0-+j|HXICF2@jm0_AcX#TlUCfNCq|6FE`jhfF=M>=&SY{XJ=Cro5dD_;V1B^zh zt>vi#uRDa>=s{z3Vtey*uW@4Vf8L(we@GYpGuqzA^~dy3{KIm&hq&UHH`yS#jMD9F IsfW-07q=v8kN^Mx literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/images/bg-table-head.png b/cosmic-client/cosmic-ui/images/bg-table-head.png new file mode 100644 index 0000000000000000000000000000000000000000..bafe24548c25dd2c7618c12053dc48d09b7b8273 GIT binary patch literal 1089 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 O2@G^hwQHeHQU3+pfHwi+2cRX*n#c?If-@JE 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\yggMcF@Sd>pJPW44f9d^UP#|sG#64 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 literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/images/destroy-anim.gif b/cosmic-client/cosmic-ui/images/destroy-anim.gif new file mode 100644 index 0000000000000000000000000000000000000000..437876a68f723174d3a07e15c048f841c54bca2c GIT binary patch literal 20116 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 E2C58CX#fBK literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/images/gradients.png b/cosmic-client/cosmic-ui/images/gradients.png new file mode 100644 index 0000000000000000000000000000000000000000..d6b9488c6f6a24cad995e8d190004ac8f6dc8515 GIT binary patch literal 11447 zcmbVR2|Scr|39RNP)cM=ds(w)rzDlFEZO%EW68cV<%VRrZ63{WEqU%{|t3+_x-&0{lEA1`JB%=&OFa^&iVbmzwLYOU(?lOW#(Z90I*)Ud{G|& zdN=?(1etb0GZi_#fl#;m_GMFV09f|Z8XZVZKkT^mT83ZZZztpdx~ z$h0T%veb1M_@2BA0{g}z+L~KGd=yi=!+iWU*vnLL^N2U~7#&y%Q&JK>+Q?WB=mL6K zm_VaWO5<6+s7?0I7m_UK;J?r{`aG1=VuBw4YC-WAa)8<;I(XW%lLp{91F-3^x0?jl zM1jqz@abLvPg_X}rUT|rj&RaFe+UHkJ49auw-iAk_C|~bFgXhjIBGS^g8@-+_KLCN zWl&lL8oT$hlmn(c;OzD2duIScFtB-Z>{tN!B?%n3^vzglUbuKKK?ssmQn?cPxV*+4 zb0$$wMq^_!fz#brxP%YL+S1!5tBJi1OgbcaN1A_UJE>Huy*^0ZnW#dt7 z)}o&qc5NQDvzy$Q!WP_C1K@*C*t-o8@d_Te;x4$`M&^k*hFexF&wa@c9Lw3&kl^{u zknzH8nrt*){E8kNocj22;A-;)bK7>~&<&^8){Vwg*VQnkb@I}9{lxJ*l9qQg8CJ$$ zcP?sY^I;$D{>5%&Ize;2ie+=1zxQbK6+5(v(1FS89G>wPlVcYovya}t@aRm-iJr}y z6L=BI4i&r%D0NYx3w|j2($ap%vev1+CO(Z5WB|U^dc1mnnrR38R>TLa|0YS5s`>l` zfID7!tROaNSben+VEJn!NwZjr`ae6Mznykc3mk^SYu@#fbT_FiDV z3Fq^&Ia7G|!s*DaQhp&D@%L){(v8<|MLxdGCidE>noapO`PZBuUk`oR-Px;-el1DjK_66bhf?MoE z2`cJsXB$6pGfFkPo)v3tx=QBJq4{ir8f$9)ZSmR$CmjsaNN;o+%3!C ze?ibz;QVD1Z4=#r%P$4_j!W+w+I?>Kj$f|7K6Ui*ORdV2HTzbZx$Pt+qD(G}9P8&u z*e|{}I9k2=+;NWk>XN6>ztUeR`^~V2qvukOyZ*5mk-QY?%RI*(za6>>D`%8{A|fdK z=HAD&k4+z?KJtD%^39w%%}&kpw-tkl)B5c$R}W3?osyo~>GV5po{q3xTj-A^AqCJ zjz#}HtdX1%ZP8Ar52bK3ayuRJ=Pr4sopY%7!J#(8v*#pcpAnMSlRS(}git~uoDbWX zpI&`>@+q&7xM5y?PX1Uvi_wBnhhajVl|g9nB_k<=$-Ld~6AO&;)2_`L@ESPfDZY%# zcY5hzsQO!&DfRv?(1i&zMY89P8aGjWDDM_L;#;a~yF zPede|6kIO2UZ6RPku4_XrsZHXYE3D3*v_|IJ!Nr9(wv-yg|X!B)H&7Amc_&waVHb( zUhII>Bx--hnP%rSgOpxPCoU(ncf>;UgZo-{JH!wwVkx|>qi--Dulg)`T7C%XSxQT( zNO?_sIb1R)JLfzXHImUOopC1JRLcAJrZ?*2*Qeg8vSd6x3&R(N>8b18aY7P~iQ9`m zG%muGR%QF=lWj>{D$6Qf8IvlLO6SHT$4sp|&ox%Psp`;c@<_M7hgT0jws~_iX!9M& zg`c2v+HwER`8($Hl$D^6BMtGY3d0V|<%7iYvCq-xInO8DExJ4Ps_4kAH(!1&IzlEq z>wehn%y#cu*r)#A8EvO+A2fySIdw1Uo^hmV{OxEHo_7)m8UJ@9>fZQ^YZntQj-@zB z=*gFeUX>A2$rp1`8ah`aU8CA6=4jg4XOd=e#Z`+>UQDQ1*4?!pzfN3;nn;^qnGEK7 zCjQ3qcHoQd*NgXZ59Wz+h$tkgX-DhIJ^6Zmuh7ebu_wOm_dgNO9Qd05wbN1Yce3xI z2h=Tu1a!Y$7SaiN^yOilOyKbrscexc^e?CKey!7H)gDbzNI#hDC+H@rtbmb-c=YgB z&kOlSbRMIeK08f2`gIgK+$-N`aeq)HF7TA`Ev(|#!kwm~%%}@FpA5}T(jVW}IFIVym9A=*6+$kJ+ zV^^V4bqq?#txvCjXpATh!<-D#f!(os)qeLCk`6Y@V#GXkFLZVUN3_&45jVvnZPy~x zcvU#>#}pqJ=0b3W51bwenBtA{Xm3awc~Y5I(2Tx`b?kARnI02OH)l)CM@%Ew+*t>Q zDp~P2Dv3+W8pGmlnSzH@%{Xnl|+i0Ws$C%C$Viu&kic%F*Xzq z{l1UCN7+z6pi6Vi-8|!$h+}m0ak?}zu4fy4~)Hs z{rH{u#a%8We@niUVvtNRw==KN>r{Ey|9LEEU32&_d#bdOY2eib!?(^uGk1F*JAK2( zraesy4HH`(u^G-p1rzMM@h8MRj_0){APy^MmT6QGKfB8zTdxk#Ltr|K|(uMl{L0?nPiX9xf%Y ztM5!mO6Z7Wjhs!FQ<0aSQ1znDtmmp@Z(>iTb8Td9bQE(P6c1bJo5gj%d-m=H;v3>| zgIh>8^>gFwp_1X&*R2BeZv&(xtQK!jh_C!d{P{^)cy_&yA%fwjH*c+$EgAftOL=_g z@#9T~9nTil?WIJAp?p)&{<6Lf06`}JfJXqZx(W3&0QjB-;IjE)RVIdRDy=fWhra3%cKr*!=68rr`Y! zCh)5W_)NzD-f)7b1#cq&-W&i+fF2xYfd@e=Kib*oP20XP;M+y(jewFc_WLR3NkKZ$ zo+WoShEq#6DHUnm=J%tUdNNOXe6^AuDcx9Zcf!(s;Hy;ArQRdsbR%JE zb#KXuak;A}mqUyA&(^ZQ*^8u^Ji&6ra>RX}S7!%EJHSn^YxDp>JEb)^=5)#Cd-0c$ z(2vp%!=MJXn%^Y%zVPbaKw$*^+Q>VYL~O>wNG`GKkzo&t2- z_^7@;)(ZjE(Y*=&yu9^`Wgf7MYNHChq@gI*Q_G?DO~tAw-K!GrDypoGk6Krz(}9nk zAs<>2Xlp`>9>2$BNCyNBK*+`_!3Y{b@)OwZw`4C0RIY97A59ox;o16~kG9P}PQcKp zzx@Rf+6f?hM8WAm`Oe0pkd8hw!x;%s{~I&B8&curgTQjlX)r3&L zTYT0XFqx)p>4>vfDK0GxIB89;xP;;D8sw4eyt-c~t&yxaD^>igO5vV}P${5xa*m!A z2tu3vr;P~?z|U<&ybZrX)MIN0;8)Gn&x)ko&;$4R@ZpOl+hAPq=7(k=6B=AaLsb?e zB&U4nSs{=x~~@B&0#e++;Cg1RPtEpq=2>)dUga845g)%Qae zAK|pxu&(Hg!zahWKO+?z62zJWHNjtT`~wYt*TZkLSyPJeOJ^Yy*QWcru=&-WcH%Q& z;|rEr2g%?jGaSDeQ9`rZqhj=QU@wG$f7*T+&+@00L2SsS?UPoG1ueuW%IZutV;!cK zl0D-N-KfjOeI1$sa7wXAKoRb1`XSqAE1}Q0{mvbHefAAMv0~ftaRMyO)F2b>rTIl6 zpd!CD5C$qVqk&xEsco^U_%U(QT;g|RsQ9r70=438cl?J_t)cm7)L$lrK{^ergY@Gx zU_wgST-VDm*a@nmVmL~rLH*`ft;fiRWs8%MeU;Q?Uwf1JbgL#JA5*7G!8&6%#D*CyqSE?0P!}0+b!60ofJ;F3w`}~=D&z}JWL_-)U_>pP+iYb z&0(fRjPX?kyhfoHEvq|z!HOMgHJ#(PwO_P)&8(i-I-4+0C+UUNP9o_aYU*v!S%>!%) zM4AUU0~yR8di9}cc{}LRR9G{eO~fS%1S$i#z#Xr3haP=@;^W*Y|BD6*xL571@sHKkWGL zdRb<8I)s2frI2C30N@sMUn8ovts`V11OG|7*lWf*b!Br6i`^sD6nWp)U=o0V3rQ;& z+09Oe%n!|*>Lr~9k>~v&gf`1G{&nj=ALBoaf8DATeAA>r+@Z=p`t+ za);AkchhUnJo~mTv5*JeJ}*sJ+u72;1I&*(cnS;LDF*VC+diM>U>W`ePH>xMNl;D;i7?ZX43;_}k>dj!&4Y z3e5MAFDY&2X9DnLa8kTYB0YHT{CdYecB{C6uf!O7Q0jDIB48D98WAQ?zuF}cTZgiv zFOG$|j3BHFLaj??Tvj*GFF4lo)vXy8cJlHHFCE# zFZWAtk8$8;!6o$s3d~(#8VMS;tiYS}hPAf`it&mNywveV?xkr@y78DL0s&QIE=HJT z!_wQJ1wh+|tIMiXCN9HJd6JYEuJV3k#&npoua>to$6QKx{Yp>IQ%PK3-&(F+msc@A z`C!L6#|@>phjTAEsI|QPe!i7{5956!`gHZQQf`@sn^jpNGxAKw)EzJ`ISsy}<~>Eo z99~GSqO7UrwuWtyNc1iTA84vAoEw$$NI2g1`ydd<;*N1Q|8l^k!!wjWsHJd8w;;_?{i2b2_d>+VO~4Z z9z(J!PYn*k-w2mXWW~kxi`7altd^grSk=aw^=F(2x59-v>TtQ5%2Q$W6~1Ba`IJhW zY<=MhrqETesH$MN3@k4@f{ARuljTV@k1L0(0NUWM_dDtfr-1iO!- zbc#~;Em0CoBR8kA>!i5BJN9rODl4z+}Z6|llEwaf|4SGMSIkd z+hUSpL73%$unzgl=R(6qMx=|;hRy{wdVSRnW3aaU!Y&3k`dx^Dup*MUa084Oh=g@N zobkOf^+*;sRNQY>Q(Y-5iZ{5?9o)5!V8ff<5K9qt8LDM<58WM<;6_d)oT_r=YG*Au z1w(|-%5|{D!DbyQ91ow9SZ_CKWP{}T1^+RW=Djk6tM25{f>zWO>`CEemy#s3_570|l(?dzg$(4l$$OWjPS0OJR27uzbbp3YA5gPy>WmMCHvQfyr zmMj8h$Ylr8k1uV*!)r?REUl&)P^J|)H(9m|93H?|o*~F5^o6>t#wu@?8FtSm6GHlR!@Cv)+QNzhQO??xEl6xB zpE>zzixNsDqr%WtHM5+xt0>FCj{i^!4(-4%H47s`_`;x<%_yCp$~>`d!kOj#*rE)H z3i0TeDJhvanO^hZRv0cDjT+P+PNaUes4X$YUA0fiAFM}KW=U2MQ+b`-Fp)`t9;2I@ zovMUTcZ!Tm33B)znyMM{Y*oyC$HGWKnG zlAFbgKqdGjM4CVmg_iNZY0-ef%UcU&1`*mrOmIXPEvJ=a_)!+pN+C2Dk!PrVLz-AOE|1_Z6AR1 z2d{s+&SxFE)VGpOTCEQtB5@Ph2!VWN&=?;|oh3ETe`rEXB5_s^NE>r{&6=uf5>Rj) zD1!>a7{MaH*5|`m8;*BXAv;q#J7#6Tzb&>CC^KJPftt}+%#%2z~pj^ld z#ie!ssa6@;ItM=GTqz!y{-tT>XGu9|&qWJ?bEYY1l3NdNXn%3>%v6dZ9YKN7JW4zipX~XR zS3ncHdU}5ef^+f{TFJd&vfAevsl41#OA*@<o(6JGe~P)%Co?u0dM;1RXb9?M zS<73j*Y*%7ol9Zw3`*jX6vWJR>#9-%+@7sk7>0!B@mW#KE?PDOn97A3$S5H9*kz`_ zE3GqA_5WP$OqArKgu9P;`=+E|%SbTOOZE%U8Fmk25O9? zO6=R_v~r1U15R!6Peq-bn(o$Ia^Q_g@-1*w#5jhU>(3?+;lvOAguV4FNW}FoJ`hie@5yU@_aQhpQKw5%L^v^c<{D0>P5kQQ%GD@T_~Y3w zof0Y7G*ZOmhSoIN$1DefGJv6ai#mS8%`cb?==Rs&xFJ&26ByPW<9AIq--#%Vi0h{& z69;-ir;Q z2HpbWhQr(iM2lMIJmbzzkgL6OLTFP4Dor>M7W&eC4wl8m4#R!Idk(|5mFM`8!{i{UdiL3J3 zU+Q4p=ud&9hy4pCC^@b&+@dHozJG1GPPf5t2$$Ygx~!s}J?Rx6-&^$?Ch=)TfwQ>7 zR895GRONdAF7lEbV_Dnkts-JJ0aae)^t@gd)*bq#ETki{Bit8(imP5c zUF}sZ$9Q|LZf%INbVFBd$(vCx)W1W1$kT1$ePw_8tg@_|Hv!qN#3-A&!Sg6@R=joY zzCx9f3!>MA5`CYXYIYF0U_?oMlN>yj*gB-4-eq3W#w|2tLtGHa`WD=-jnSH_In3!> zBA*-Tx&d>MH!kQWh*zmBR5`9TU*?V7IjcR3JdVWT??PF(%siycYyEofUCR0xHon-2x?o0BLFC76b}8@>Hmqs2*h0dlZ)4b|Bc z7i`r~?uRbKpl%YsA}LR--12=)*c_%%_HQ>Fh*IoAcx$mB&_pArO`kwps#}+aXxin} z&8vK>bvrvhG;d`v5ar!GKch#hi}V>9zby*cY$=L^E@K&3t=G-XoCjbeYTch|+Q=aY z8jBb36$S7tK9zNU4I+QE-XV%>XY9gzN=9hT5@mYY)+Iz@URiYEtW1%5OF+(CqAAaG zu4AC~=uBnybWuq{U!^a)b2MA&pxDAJ(j|$6uB}38+jrDyk0Pt2)!^RvqiSB70oqwF0i^2Wo-YlWtIIddS~E_Ld*C)>CN37@C=3OUAKHC3S% zS%Qg}U$WKW`P5T;6&Dsx9L)_L`4HGq`wmq%X0KkhC^oH?7;L&SHs&uz@^zX__csw` zsc&!?T&y1UaxOGBE!%J}n_Ifo*z_Gv(x2#gtoHg0q@iHrqhi(!Te$@FRHIha9nXDCKrM3J z_;nT(O&_7<7`N^qm}z?AP_RLF z3RHxzFoItn&{Cq~S=*fLcLK+R7VDuq6xJ33654fg))uEf1Taf6lX2{(q^tc-ePk4yxqgFHhk~q2MD`?1E7EN# zX?Sf6S@JXvK~DA?2&uu|Hixe80Vbh|~pde@GY*1-~V8;cebc^v6ZiVb!q0ZF+KWKSZxVjBM;H zBWR(#ZKZac-wpm2K<>Ts53JV?Z!`M%Kth_Ha1-Xb?z*+2$jLD-six^Lm8Ia0(nMS^ zuR>kP5>hm3^u1Hio&w8V>I8>Dd;Tx;^*v`pW^J`pjNP4w5bsH1P|}84>R@cDBBfJhw=ExS4DT5Z`}FK zCmk=Uu7kce)i%nKg=te9X(YQZ@i(!?YJQs3-Khrc_43E(KT+iWfpGi_zY0C~gIe2S z_d@;GQyM5E&-XyLP4weU2&?ZmJhLQB$re~`F4E|8ZMtjgJ7^_sRy6z%#Qwo3x2XPf z3A^Qp43$_Qx-lt>qPE4*YS3@djo;0phIxyMr1`b-KUJ_*(m@X6AUFYevhP>Bu0I?a zd7H4Lxs=OXfD>|+rr-%Jn(kDiIiNRFG-06SXhgRtsA1jnhkr5$hC8-CrvaH7q&?d8 zF}j-efMnYQ_gSDOwB?XI4?~OIq}5Kxz4^MgZcHGmG6K@<@rk`>|01)W<@cY)G#dAR zX+IG6Px^nL{-5FSpSJ&Zzx^*qqP_D^j`Q=g|HIM#ZtMTw{Qq*apSSwojAJRAbf8;a Vq~{!bF&IF9S2T1l7GJQr`+s3VV*~&I literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/images/header-gradient.png b/cosmic-client/cosmic-ui/images/header-gradient.png new file mode 100644 index 0000000000000000000000000000000000000000..4f5e88fe6c0dcc298843446b6d26df105e5b8270 GIT binary patch literal 62651 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 bJCy+&`242O=I zbMa6YKDj`e2*zJ^-3p>4mj7Lg7w{di9ddI+r3q&TZM-?)7FV_#Y>TZa=NZg5nEzmG zU^`ypLBSE-x@s=w`-L}pBH!wyCApEX?PgiMI%;fyQ1ErP3H2WrT4;OIZke#o)8QYp zkv{-T@?Iz=2V0A$2j&5t$2tT;Vqj>%qnkZCxjRD{CS6Lf15vgypXGttV!7~r4q)zJM8oc=%T7_QV5~4uOt5-yEc61E(P_0Z_k~t{*hlzi4~fRf zK!$SIsc^y<;u9ZUd+-Coa|sNLhq#+Ts*GBmd&;9OaTu300r3m*?1n$eZz(Z`QUc2@ zntaP@^P^d&-&;b>Dl!YsP9*%@_{;$?asVzUU*D$b@e;r^+}>8(9cVu|IA~#OH0L?; z+@l(`jJuojhWj-{vc5E$*Q*1+xvLQh!TO`(XZOqj=mAxX{>`)*y!_GDa2Ea>vMTKq zf6%oCC)KY>?2=XtWn%?64OSeOfeSUcBwMPYVIep>sSE-)#)D4`uv9hTdN3gAR6OxKCfh#+k<{Ur0hyHq0n2rL*G^c$OP-afrzubN7U0qi4#+PYzNSBkF%q} z$NR-865vVpXkni2`JdcxIo?{E@8e<9hp4vKvxgwA(pejD zWZP)>9|cOp)=+hO&aF4FRdmw39e&hUQ&e=Zt2~!jZC{EjONpdS2I_Bs1uUJGSBgvy zT&;lssa60vJqy^C(<2aO|KtmV?EE>2?s2nv@D|?5-N~-WeWZL`y21-@n={y>G>Uc) zcYXi^@uroJs&965zRzX9wa>L-ZwN07hl-QAqhqcD-AbVx|5SDwm<0DdU;2P=xdAGdgje7;_f0X%d3I$8X=kELk zLp~EVco=OTss2p6OYbTX_U{+0t$&}^6#yhj#R%*VsEYmBUhsYzc*5zMvtbEsp~n4` zJgIuFU<>3<>8lbl|H`0$b?FINI(2O8H0G=P`(Q&l_vQ%*@&9P_Nxv0B0T}{1?~`ug zh3P|_SUb;GcWRd6Dg~HR%V9_Cd`hd`&U>VkEYnchvKQ~G`2s`Y(N<);DXtDF@pIoG zXmj>>XF>C!JjKn!lmFCU)UWnnfT>Ir5{nm9oz{+wDGkbW1C?APjw<{$&XiOhm-4?C zkIx57`ORp%2D;1!n)wEhen5KVh6_$_?LAuxxRwRp)7B<0D8DZl8H)(9x*SULu7VjI zl%K+z;yrjr0W<4d51JI<+l$*XK2i<#-YBs{y;%?XS0z&GH_2r9v~}}3^f@Unr1dI; zaP95=AmRgsh@qw{aq|PiY8W+vr`pfc5)))YCnA|P!Aq=fDGZyq60xArk~K~-rd;esDQn+OkNEFv|NzM z_kQvF{Cq#FR)?xO)&vk=^TL*udz~0+T;=B}GrA{K^B5zhW-3XFEy`D+W+Bb84pU)k zFHV|lZwMpnJW{@nM8jAZ`@Uj zx!7a!=4>`~Ks96N)n+i&*TvO7c@#d-5ACo%76!`<4qv43Nm}QOjFmu>4u&)KQ$D`E z$J?($#E>>H0vB|is+_7aAbX|+tBR^3!N=PoZ861;17lb0lQ?NO)~N+0%zg-pP1$dz z2E~=@&%RjeG6uZ|(5kyKq1x8xC?dnr5pIV^<+EtgFeiAam#z{s)?ni%uC}>{94}sI zAoaPS7XA#X?M+9=g`9g%zw`XexD{hOV5**F7ngazg6dJP>c*s>V)ws#L+1ABi4#<9 zJTLFY&Dd~Bq_p(mG1ANd5g$`^iv=VEThsyyM=6shGS$ zlb^?|hZxgKJ4y+)H!1v=e%gb!5AWv-O1;;Ik~ua)LINozJy!bGRF<+0zr#h!W*C50 zkk$eNr#@H#Eqr44$HwjWN8s`1 z4Vn0lIzCpM)MTyXTh2oAsqH=(i8C-eo!PBZpf^3f$!b47!qJ#6Cp|K1;PHuD4QF_cN3K-x6KT($q(ZJHp^nyP^=`d@K0*d22N^*yB03X;4Tp#+SSZS}H*KnH2O@(rH{tT8bBBmSyZ}95y z{^!n(j(hy*zb=9M=(RM3kPO(IT73WHr2hG6R1;Mg!M%I5U2^^nAV5GXCf@F0g9x=+o*gUh{iMw4IGvzB?%lWFGtCN-3^|Y6tQ+V)?H&expfxQw z4}!{NqglExI)k_zv%NVIvjVdYvY-WWzI^3%*OSF9+bZa@uUOyayVGmm080f8@-Ff6 z@)Cfr#XdaRp>;fD3Y|`Ev&S%;N&<(hQtKY=>kZJ@OLgf`(6vh+;^@ImybFTO%|hw^ z6F#H_Ueh+mmsgVI%`*aj{)pr}<(BW1U{a&&4)%}wNMyRMxBJ+qT(ckVukQm}eLSwM zEl%M6_5zQUfRPnfK@KvZXle8sJ3~UpwJftdBe8C=fXP_QTo2{Bsjf8@^kxA(aK>=7 z8KY&|Op9IE%Dw(>lmBQ9p}RM;#_h&=bS6kIC;LMZxr=m!Z-NGx2 z^-b9k8>uuJV#30%M5XFWrL^ee{d52zMCUmecf;bLpqeO8I!0Bwh|Uj{f9jy28|zhoe%@i6)YMB}lv-yR|!=G-db*U=1&t{x# z0WRmGTchKkm9C??%CO<=ziVR;?0s%Qi&M(}qR_wU7A_Py@$xK>|NIt(-c zH4od%Q&f-=O-ivYu_)}}-i%k=eoHL8p<3blGO#5M3Y<2kT2|}vlW0NM+@uJv zpc@e6rTDE;1h{eq`?&3NUmO*9qSJ(Mee$56otuSu0nhQa)dLB2OpMx{@z6hee23JX zxJIcMLLMXpGU(EnTs#G)Q+UKF@9*X$pgnE2vX4+xRjkU$xdZa(At*~wkAZnR#OC9M z*4i6nZx&OQ|9)DUk8D!3lXDSW7?thfl&&11UlW9U)}b5FaGJu57%>1sBM0jU>Fj0R zZuCTjSd0`8I|WyA)x317Xv61uK|evSt>s0xJCMoogV0G8PAb3Zi=;26f+s|ah7154 z$@CSqzMy6`Ix4e^j++I_@$kD@cf}*^vDXrG?8+6A?I~?yE)%1>aN7%8wMm@wgfm$B zlWy`)N(@~_)+DNPD6W)sWXC8Htf_B*M>a`>xVh!+_vpr))OD9tZaR&?HU#`QMgDxU zz`wt0rX9C9KI~(c8XC!AQ|+oK{CWew&7Eo3?X%l~2xArx%fddw8F$y*h3u)Ag8*>lLGe#D8Z*_hCR+)k$Z>w1b9i=tQ_HcX6_FNdN zqXS@b=_w)sM>(OKR=;^qwU`J~ej>!QPJpYOFh39IC)iPr$nm_AayS^KWPd#&R-)n` z{AZ@MCKRats@8uGLGIF{`n>uO(`MC2X`&MKZ$u|k7aY;(SX5J4EqH{b-%zaP;HYA+ zAnSLbmDq`0nC#X&9mZV1Wh*YmG zy7P^fLz+nIa?oaTuHrLTiU2J{|McGEf?SBkxp!Ummx&6;%Y&5uf1`x9SmxyXXc&S7 z5#~1xvK0%#^(RX(N2{tfyE|JrUS^A(mPi{{JQQL}Z^_0DgmFeg`Hw992t>Ss?XD4WPxj_yBzuhItgndLsom1ahGmhzuork;dOtKg@mok;0-R-o!cZ8Vvp{ z%EpLJ-c#T`ae_B7M*a(F&`5Lyot|8@8pqK^-fI3MMMZ0#BbS5lX}9GO$wNRZj0Ns+ z<8be2fry8WanPxU-q9Awk_}mBpRnS8Mo%xl!h^EHj#TRuv5PKl*r~ChaJL%b zOO2INX#ps9ktqM<7s+ng?Q1N~Xg7Ln_be;C++!ALDaZ)d6bcKcDvx5Xror5xp~D?7 zq^K^=(@Q*e!`9?9`drQAhkE<@_D}rL&<+%Zrpp1q{q=*Qjk#q&0P9xm^4ntBtrO?} z>AUK?fS5{U8*bBJ7s?rwCco6MnpYFQ5vuLY(1mXiYp zOb-|a?2*5ueDsP}@f~gfKl#P|tWv?=zGi!S3PwY1t9G`y?&Bx4pxbPl=Kzr}}U-Rm?FZ}$^0t0mx zJV%nXz&BgJ)+su~UDTSd@h}4E^y7dFa(nyQ2_*s!23*rFQTWv<*z@OPWn+R`K|Wn^ zi3{)qzG4Q_9b@54pjuC%5G(#|6O-FR!|Z-mb+aA~mMp~uk@LDKzW`yacK+bovOYc% zH}+b1#+Q}=3#)cxfMcDgYt#VEOo_$yp2hER(m)O+B7!a6sp{;+kg6HsrO?e?+))G` z7ISCgK~Y!dS7LC?+8}?tF&E8~T5(_49eZ}+$CH0rWmr{z?hfRp0e0=5nQ5zIBd+c8 z>sqMf;aL_}7Pz%Gy<`dZiK@|?WstbSK$`s8}zk4lgW z`Mfl)>&_1HggTk?psEdOx4W!;9xP>6lLfwz{aS5mJ#uO-siTqDTSU; zeLZbS-5FWdi)0mjKeczTdNob&u_{cOBl^c%t+!c`G6AivP?3Pp~It57kEZ)9_|GP|0pmBpYQrN>aD0=E~`eY z&3>;Mn3&LaSvJ4Hr8DQrgDa3vn;QkxDa4-M>mQEfkDmT!q3yco$s_x=qR@DK1JUw+WrCQ)(RV48<9&+FAQvybp)9-`mI@tQ ztTRU$$`XH4v-UeO|Crqc7FO2v+!Vl?S4?|t8Heh8n6R$!s!x?EtJ-Imurk4Sbl7X~ zUqR$F$1K>=d=e*D>PmX>A^-~4vGRviN(7+JfUICEKxbF@XZ)V)822MhFsmMjB@@`v z70w%rJvJ|Yx#2N8GN-WHdL_1;r;%5lEn8|9h)RD0%u@a{z%9FJR&1O8^&IoOW)kp- zhx-AHC_{3d&hIBr`kg=@gxI?IR6c5Ri|1i^-n0_y>m-ANjVyGf1={&}J~5j^n1P|Z zpAQhe>*|0H-xy|>o{pj`Z$fMBEvI>$CSYqXE#nm1n>yy7>t4fuy2j$UuXRGO)pjI)mVkJ+^DZP+sN-95Yhy9GR<{1 zn#RT-(OPn?c07H#0OoH`d0oV~nK(SrXtKLmU;kghQ?wF}&LJdg8A?ZqaEXwV0~8hC z=x6cuu3*O=hWxp?gzw4CuV8bu34J&$Bn!Pr8u#IT)u$3Uf1tbuQ&3mVE*R&@h={l~ zji~f0CgQ8B{wc`;v9ARjc#upq6N_l{P+@g)j>5`yShBS9a8*^=j>?5N3SUoY;aB-+ z=O~=$*z>vk^UJfzTG9fJPQfbilfm-ws^r@$nVBEYFS@aR<5bU2eeO@hcn$-_d&Gy@EztXj$haR+&|gdog4SL z(#YloTuld?pN~v_R{k@>KO@!t(cs8~)ig)sYjjrDH^3tXD+!duABlJqlMiNxxWD;Q zDJD;zxTH!2?`Ko7JE&tTUdE=&^*BxZ4qzdE`S@`xUWhHKZ#V0B3|5e_(-yc^OV~@P zpfpripOYT;F^q4u?r?#ru=rK{QVdY&6xse`A8F&~a+{zX;x0riB{s`Rek>b^XL^q500Nm{==0~wRdx9h0JBg(dw=fuV6jZAdxA6` zcnHW{I%FR&Es#)4CWs}g#sGb1ZAX06`n^LdOgp%lb>lrYthBUz^hl?uM9dM3 zT!UGQDJl3jW7Lmj0WN?Vu#~WD3x8bWMBr8CR>*oHfxxQ@GBFM|(1Oust!0l4{_$}J z!*LtN1}fxayK?_7YNoYKmgL#0x}yCz`wlOFY*(b+^*5sw1B z=n43(--CE`z}D9RoQNnQ^`qW(%Afenf}NcmOZTP4O^L<>K=o`3x^q`sFxY#nqQs0! z3v*B2;&qQ+@&!o9<_ILd%2_w+XID>LGZ;uK8{$|T1U%1lVaLq5X`Un}Ju)?JY4OA9 z8^Z3$6c{=N*$d^Pv+<0y`*rm%^sejBQSt(dYZ=$Ez5OBnB5gU(u4;(e+R!wjn%iym zK3kTq=#9 z_g)T>-hH0MFjv^hYOg6hc#8FiV-A9Q42T}>haIdD#+atp$_8@pAJ?GDJ@icMIbFq) z)f{@?!v$3Wys_Y;{c4$PtMVCn%n0>jG(N+t*CUJp!y2l1iSAHfSKMf1a@G}1-f1pX zj+b&Mky@4vtmw+b z4fB}h?=NVAgG)k|M1~wHz4IPF$$K30t>hXI{Vu1yz8ePkh@Rr1yPg;wuvHvAwWwx+ z^UA%Rb~D3L$i)fB`flWPL{Iv6XjRX7-jP4^(lG)D|J6}&4LB_p?bTi0uDI>9-FM5n zB5x1P#1*ui4uGrrC`qfm_mu$0*!kKvkp0!|xq{c$-~7o$1LY&b9`1&HE*qhD@SWPE zZ9@s`4S14?Ng{g(O&kH$g!E_L-On0_r8F-7T5gL?&sre}y2P~4N_AD}DRXswc`Q;~ z@mU&V0y4?cyN<&R44@WYCmI~JQIpW$6EK4Y*>GRu!PK>a&MpUl{x++qq^LY)Od%^P zJ8UvMWX%bDAHFaYEEC-O8JTjtj7K-H`=bGHD+=e+(aKFeNQjo` zTdF3063aatH6Ae`7I}g_C z3mgqq)^vok?est1-m6&p_)!XoK{ch(vm;RMM%Cb3{pAiTAIIDD(@S9@ed|SLU?Y6p zY8piro=ifbk7~UFyF#W~1T(2EIm40ZC~_=<>>=Nbdp6~7a}hXo4I=K&)lWH>`_3Uk zrJL%|3y?ov$GV}(9ht%6eVd*b8x9I>&rsbMsSZ%3136^z*B9!2opbL~qH}D`fZa$w zvhn?N9|z-)`|}*4A%hTN@+2vvtt0lhgaAjgShgSsZl}ftB|N@v8%#DDujLgp50V! zQXg2^zH_b>5%LG-M{ztl5o>A!lFcp3-|rl&tD8A?Ttl_w-DFjXJMR6y(#3@=n_8uM z{Ny7WZPJyuR9Cn#7a^`OkIJvrvJKuI_`s_l)AnN|S4yh&#~c1f=Xt3-!(W264S zdD}?-jUkI;D~})(lWiNsFu{Wle{zGMlY05J@kx;%6%p^_W|zi;5WI?~-OMtc~db>+N?nF+pE zVI}5sep_45t4L;%^#gJs`N*+b(aX-mV|_3!4RoQDP=rF2Zp45bdqcL~M}l3256Q|Z zD&&u!oA2%gshYTAzS=o_?VURaZ^y8t4Yy^lE$yE@>-4dT|s0;aDU{nxD{-AyMWg+YsxLyw^A(7 ze36=r>F$mO9t18qpU2J}!zUHg5eei5V9*OtuOZxX0)g0Fkl%DJa*kBlo`Y1Uq$KI< z=<8fy@1p>!-%1r=(W35I>cm}b_n2Gvm#(O&%sxZ&7%5*(vl(o?X5C0ylyM9^AgvuE zRD%!fx0e3sdVU!j`#1?8ws(ht&$!j(*1*$$zvJqbmQW$+Sb~?3IY3)YOhwr+z zdt6DF`?y3mOxL7&hl4`lQVUqCwRaj(@z_;z$sAx#~tMkB+|eZ)E&4RAjsIbdOc>YgG?eZ#UZe+|gqel_yWDOPw1-SK=&{ zk`C=1CXT)g{C83n?32ujASA$-(OX1xDWmUIH@pyX&huE!s@&Gq3) z_-?nskm$-lb7&$Mvh~RPSAllOi>+7D+(3KK2o-;-M@NZrAZ>^Wp&;1_-+>hAjNZD@@a@vl zjyQftL|owUlkoyS9E!|whDMdu=lz2`k|RO>aC&PLA2c~BE+ix=CMsiWfpmFf?dnX| z)!X}PXQj&P+O5Acau&ja&pwYNFU#uGfqvymLpO`AsoYp}Ot`Q6^xBJtybM;3yjzp4@JQTOINl=Gh|jLZPWWw7{1^gu=@ zke|ZK(~GsYr)Teoc>XFb(`&I+zE^cKBTXy#ZA1`hsTPf1-f)w<6SrA!?AVz}hK_Etx2=}g(B8uvqY_75Cbo6yiWF#e3iLgca&nvhF2Wy7t>L$u~?h@Mz zGH%yeXy$Toa`7i7BtN~qPkdCJt?IQC2?C9#Fsr$Yw4ireV~hFPVeT7q>|C*THhwB0 z_6B^=H^jv|m1biGL)3h`A9)bS${xQMWSE#31%Gz+;j)r4s~jxs|2%IurS9^$Hf-YL zwA3c09lUtd7;wxmYgSF8>1xvB+wNTKbM^fjI#xyV6!3m5PIv5FCZO1+MAB&Y=w%IW zae986vfsxFIE~?f=i2f@N&bTX&Br-;l~9H$ii2YbC=VZ`8Z$yi2{Iz&O9RfQX z;_zzvsgB`;4TZQ(A&>3L6!cGl(?XiT_Vf5@!vUfh80Wf+ogYI5KHA_eM3@l=4M|rh z6npVL7-2ot!oI1?nygF4k1LQ{pF-BMy>`+_*1_APMpg9j_f`ZdsC82OEAV~~8g7@t zL8BF&okjV{3&|Uk;k297N9rEdAt8VJb2P!KE58@z7t)E``+sg#JMaY&KWzqnDF~Dv zl$lX9S~o*)B;7)wkl*uBIXQVbIkwLpKI{x;ziv<`!owKym$Y}l&BbLk@2{+>3`7LV z;~@=t)~DkWeSCr4<*+-Ao~4`nWZc#s*^^KEdc;o;K5bQmgpiQz_Sn{zci%{EJb9wx z3&O-3ze&f!SLWUsmDnhOuG@Wiz9q6zdEx6-wfA$txh>D82AQ(zn`RXR;%7^0mw&m9 z^QeR#FZRLH-UIKB{QJV-+K2B*3t={)V9#PFCu&3^axS;BPf?~KphtKOy@#7OPESuq z0s2X=TV)K=La@MW1_rf#^k^}qPl=RO4vE`O(NjYDzebOShik!ah~D}px@<*U-}m8- zm|vK40ZL0Ne&%qh9HXDvnMo$Hc0c~7z3+}{s_FI)i1MHUO0iL;7Xbz70wSmc0w`UO zqI58z^d7N*$|EH-0}?kk`s{kTe7G8c2oi~xAkROG;iVQPbSLE;bwykR?%_1G>0dE z3Z6b(T>K`D(V�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 U5?n4FXbJ?rZKPX#%i+cU0SaG!ga7~l literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/images/infrastructure-icons.png b/cosmic-client/cosmic-ui/images/infrastructure-icons.png new file mode 100644 index 0000000000000000000000000000000000000000..188afb131592e3114f76bdc83adb942d160faf80 GIT binary patch literal 66823 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 literal 0 HcmV?d00001 diff --git a/cosmic-client/cosmic-ui/images/install-wizard-parts.png b/cosmic-client/cosmic-ui/images/install-wizard-parts.png new file mode 100644 index 0000000000000000000000000000000000000000..68b07ee267af2766a3d49899166d6bf0719a84da GIT binary patch literal 623374 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ᓔ+5yRUnhSukoEF7Y%0DOk$b&Gm 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%0A{7TCU>?^%dK^sb5^86k2W>WS8hv7U<`Z||`T<{% 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