From 9e5fb17e3d1caed094dd962029b206294618303d Mon Sep 17 00:00:00 2001 From: Alena Prokharchyk Date: Mon, 23 Jul 2012 11:16:11 -0700 Subject: [PATCH 01/11] CS-15077: fixed listPublicIpAddresses in basic zone Conflicts: server/src/com/cloud/api/ApiResponseHelper.java server/src/com/cloud/server/ManagementServerImpl.java --- .../src/com/cloud/api/ApiResponseHelper.java | 4 ++- .../cloud/server/ManagementServerImpl.java | 35 ++++++++++++------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/server/src/com/cloud/api/ApiResponseHelper.java b/server/src/com/cloud/api/ApiResponseHelper.java index 4c88e2b2be6..433d2637e52 100755 --- a/server/src/com/cloud/api/ApiResponseHelper.java +++ b/server/src/com/cloud/api/ApiResponseHelper.java @@ -760,7 +760,9 @@ public class ApiResponseHelper implements ResponseGenerator { ipResponse.setIsSystem(ipAddress.getSystem()); // get account information - populateOwner(ipResponse, ipAddress); + if (ipAddress.getAllocatedToAccountId() != null) { + populateOwner(ipResponse, ipAddress); + } ipResponse.setForVirtualNetwork(forVirtualNetworks); ipResponse.setStaticNat(ipAddress.isOneToOneNat()); diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index 93c2f4ca2a9..047305ed249 100755 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -1691,23 +1691,30 @@ public class ManagementServerImpl implements ManagementServer { Boolean staticNat = cmd.getIsStaticNat(); Map tags = cmd.getTags(); - Account caller = UserContext.current().getCaller(); - List permittedAccounts = new ArrayList(); - + Boolean isAllocated = cmd.isAllocatedOnly(); if (isAllocated == null) { isAllocated = Boolean.TRUE; } - - Ternary domainIdRecursiveListProject = new Ternary(cmd.getDomainId(), cmd.isRecursive(), null); - _accountMgr.buildACLSearchParameters(caller, cmd.getId(), cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, cmd.listAll(), false); - Long domainId = domainIdRecursiveListProject.first(); - Boolean isRecursive = domainIdRecursiveListProject.second(); - ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third(); - + Filter searchFilter = new Filter(IPAddressVO.class, "address", false, cmd.getStartIndex(), cmd.getPageSizeVal()); SearchBuilder sb = _publicIpAddressDao.createSearchBuilder(); - _accountMgr.buildACLSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); + Long domainId = null; + Boolean isRecursive = null; + List permittedAccounts = new ArrayList(); + ListProjectResourcesCriteria listProjectResourcesCriteria = null; + if (isAllocated) { + Account caller = UserContext.current().getCaller(); + + Ternary domainIdRecursiveListProject = + new Ternary(cmd.getDomainId(), cmd.isRecursive(), null); + _accountMgr.buildACLSearchParameters(caller, cmd.getId(), cmd.getAccountName(), cmd.getProjectId(), + permittedAccounts, domainIdRecursiveListProject, cmd.listAll(), false); + domainId = domainIdRecursiveListProject.first(); + isRecursive = domainIdRecursiveListProject.second(); + listProjectResourcesCriteria = domainIdRecursiveListProject.third(); + _accountMgr.buildACLSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); + } sb.and("dataCenterId", sb.entity().getDataCenterId(), SearchCriteria.Op.EQ); sb.and("address", sb.entity().getAddress(), SearchCriteria.Op.EQ); @@ -1760,11 +1767,13 @@ public class ManagementServerImpl implements ManagementServer { // don't show SSVM/CPVM ips if (vlanType == VlanType.VirtualNetwork && (allocatedOnly)) { - sb.and("associatedNetworkId", sb.entity().getAssociatedWithNetworkId(), SearchCriteria.Op.NNULL); + sb.and("associatedNetworkId", sb.entity().getAssociatedWithNetworkId(), SearchCriteria.Op.NNULL); } SearchCriteria sc = sb.create(); - _accountMgr.buildACLSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); + if (isAllocated) { + _accountMgr.buildACLSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); + } sc.setJoinParameters("vlanSearch", "vlanType", vlanType); From c60e4b4a9e3b7403b8e04b2e2223f12a04463133 Mon Sep 17 00:00:00 2001 From: Likitha Shetty Date: Thu, 12 Jul 2012 17:12:23 -0700 Subject: [PATCH 02/11] Adding support in AWSAPI for CloudStack API's that implement the tags feature. Verified the output of, 1. ec2-create-tags for all 4 supported ec2 resources(image, instance, volume and snapshot) with and without tag-value. 2. ec2-delete-tags for all types of created tags. 3. ec2-describe-tags with and without all supported filter. --- .../bridge/service/EC2SoapServiceImpl.java | 182 ++++++++++++++++-- .../service/core/ec2/EC2DescribeTags.java | 34 ++++ .../core/ec2/EC2DescribeTagsResponse.java | 37 ++++ .../bridge/service/core/ec2/EC2Engine.java | 105 ++++++++++ .../service/core/ec2/EC2ResourceTag.java | 64 ++++++ .../service/core/ec2/EC2TagKeyValue.java | 44 +++++ .../bridge/service/core/ec2/EC2TagTypeId.java | 47 +++++ .../bridge/service/core/ec2/EC2Tags.java | 44 +++++ .../service/core/ec2/EC2TagsFilterSet.java | 107 ++++++++++ awsapi/src/com/cloud/stack/CloudStackApi.java | 77 +++++++- .../com/cloud/stack/models/ApiConstants.java | 10 + .../stack/models/CloudStackResourceTag.java | 50 +++++ 12 files changed, 784 insertions(+), 17 deletions(-) create mode 100644 awsapi/src/com/cloud/bridge/service/core/ec2/EC2DescribeTags.java create mode 100644 awsapi/src/com/cloud/bridge/service/core/ec2/EC2DescribeTagsResponse.java create mode 100644 awsapi/src/com/cloud/bridge/service/core/ec2/EC2ResourceTag.java create mode 100644 awsapi/src/com/cloud/bridge/service/core/ec2/EC2TagKeyValue.java create mode 100644 awsapi/src/com/cloud/bridge/service/core/ec2/EC2TagTypeId.java create mode 100644 awsapi/src/com/cloud/bridge/service/core/ec2/EC2Tags.java create mode 100644 awsapi/src/com/cloud/bridge/service/core/ec2/EC2TagsFilterSet.java create mode 100644 awsapi/src/com/cloud/stack/models/CloudStackResourceTag.java diff --git a/awsapi/src/com/cloud/bridge/service/EC2SoapServiceImpl.java b/awsapi/src/com/cloud/bridge/service/EC2SoapServiceImpl.java index 9e10e0929a3..9a8e777f6dd 100644 --- a/awsapi/src/com/cloud/bridge/service/EC2SoapServiceImpl.java +++ b/awsapi/src/com/cloud/bridge/service/EC2SoapServiceImpl.java @@ -33,6 +33,7 @@ import com.cloud.bridge.service.core.ec2.EC2CreateImage; import com.cloud.bridge.service.core.ec2.EC2CreateImageResponse; import com.cloud.bridge.service.core.ec2.EC2CreateKeyPair; import com.cloud.bridge.service.core.ec2.EC2CreateVolume; +import com.cloud.bridge.service.core.ec2.EC2Tags; import com.cloud.bridge.service.core.ec2.EC2DeleteKeyPair; import com.cloud.bridge.service.core.ec2.EC2DescribeAddresses; import com.cloud.bridge.service.core.ec2.EC2DescribeAddressesResponse; @@ -46,10 +47,13 @@ import com.cloud.bridge.service.core.ec2.EC2DescribeInstances; import com.cloud.bridge.service.core.ec2.EC2DescribeInstancesResponse; import com.cloud.bridge.service.core.ec2.EC2DescribeKeyPairs; import com.cloud.bridge.service.core.ec2.EC2DescribeKeyPairsResponse; +import com.cloud.bridge.service.core.ec2.EC2ResourceTag; import com.cloud.bridge.service.core.ec2.EC2DescribeSecurityGroups; import com.cloud.bridge.service.core.ec2.EC2DescribeSecurityGroupsResponse; import com.cloud.bridge.service.core.ec2.EC2DescribeSnapshots; import com.cloud.bridge.service.core.ec2.EC2DescribeSnapshotsResponse; +import com.cloud.bridge.service.core.ec2.EC2DescribeTags; +import com.cloud.bridge.service.core.ec2.EC2DescribeTagsResponse; import com.cloud.bridge.service.core.ec2.EC2DescribeVolumes; import com.cloud.bridge.service.core.ec2.EC2DescribeVolumesResponse; import com.cloud.bridge.service.core.ec2.EC2DisassociateAddress; @@ -69,6 +73,8 @@ import com.cloud.bridge.service.core.ec2.EC2PasswordData; import com.cloud.bridge.service.core.ec2.EC2RebootInstances; import com.cloud.bridge.service.core.ec2.EC2RegisterImage; import com.cloud.bridge.service.core.ec2.EC2ReleaseAddress; +import com.cloud.bridge.service.core.ec2.EC2TagKeyValue; +import com.cloud.bridge.service.core.ec2.EC2TagTypeId; import com.cloud.bridge.service.core.ec2.EC2RunInstances; import com.cloud.bridge.service.core.ec2.EC2RunInstancesResponse; import com.cloud.bridge.service.core.ec2.EC2SSHKeyPair; @@ -79,6 +85,7 @@ import com.cloud.bridge.service.core.ec2.EC2StartInstances; import com.cloud.bridge.service.core.ec2.EC2StartInstancesResponse; import com.cloud.bridge.service.core.ec2.EC2StopInstances; import com.cloud.bridge.service.core.ec2.EC2StopInstancesResponse; +import com.cloud.bridge.service.core.ec2.EC2TagsFilterSet; import com.cloud.bridge.service.core.ec2.EC2Volume; import com.cloud.bridge.service.core.ec2.EC2VolumeFilterSet; import com.cloud.bridge.service.exception.EC2ServiceException; @@ -199,6 +206,89 @@ public class EC2SoapServiceImpl implements AmazonEC2SkeletonInterface { return toCreateVolumeResponse( engine.createVolume( request )); } + public CreateTagsResponse createTags(CreateTags createTags) { + EC2Tags request = new EC2Tags(); + CreateTagsType ctt = createTags.getCreateTags(); + + ResourceIdSetType resourceIds = ctt.getResourcesSet(); + ResourceTagSetType resourceTags = ctt.getTagSet(); + request = toResourceTypeAndIds(resourceIds); + //add resource tag's to the request + if (resourceTags != null) { + ResourceTagSetItemType[] items = resourceTags.getItem(); + if (items != null) { + for( int i=0; i < items.length; i++ ) { + EC2TagKeyValue param1 = new EC2TagKeyValue(); + param1.setKey(items[i].getKey()); + param1.setValue(items[i].getValue()); + request.addResourceTag(param1); + } + } + } + return toCreateTagsResponse( engine.modifyTags( request, "create")); + } + + public DeleteTagsResponse deleteTags(DeleteTags deleteTags) { + EC2Tags request = new EC2Tags(); + DeleteTagsType dtt = deleteTags.getDeleteTags(); + + ResourceIdSetType resourceIds = dtt.getResourcesSet(); + DeleteTagsSetType resourceTags = dtt.getTagSet(); + request = toResourceTypeAndIds(resourceIds); + //add resource tag's to the request + if (resourceTags != null) { + DeleteTagsSetItemType[] items = resourceTags.getItem(); + if (items != null) { + for( int i=0; i < items.length; i++ ) { + EC2TagKeyValue param1 = new EC2TagKeyValue(); + param1.setKey(items[i].getKey()); + if (items[i].getValue() != null) + param1.setValue(items[i].getValue()); + request.addResourceTag(param1); + } + } + } + return toDeleteTagsResponse( engine.modifyTags( request, "delete")); + } + + private EC2Tags toResourceTypeAndIds(ResourceIdSetType resourceIds) { + EC2Tags request = new EC2Tags(); + //add resource-type and resource-id's to the request + if (resourceIds != null) { + ResourceIdSetItemType[] items = resourceIds.getItem(); + List resourceTypeList = new ArrayList(); + if (items != null) { + for( int i=0; i < items.length; i++ ) { + String resourceType = items[i].getResourceId().split(":")[0]; + if (resourceTypeList.isEmpty()) + resourceTypeList.add(resourceType); + else { + Boolean existsInList = false; + for (String addedResourceType : resourceTypeList) { + if (addedResourceType.equalsIgnoreCase(resourceType)) { + existsInList = true; + break; + } + } + if (!existsInList) + resourceTypeList.add(resourceType); + } + } + for (String resourceType : resourceTypeList){ + EC2TagTypeId param1 = new EC2TagTypeId(); + param1.setResourceType(resourceType); + for( int i=0; i < items.length; i++ ) { + String[] resourceTag = items[i].getResourceId().split(":"); + if (resourceType.equals(resourceTag[0])) + param1.addResourceId(resourceTag[1]); + } + request.addResourceType(param1); + } + } + } + return request; + } + public DeleteSecurityGroupResponse deleteSecurityGroup(DeleteSecurityGroup deleteSecurityGroup) { DeleteSecurityGroupType sgt = deleteSecurityGroup.getDeleteSecurityGroup(); return toDeleteSecurityGroupResponse( engine.deleteSecurityGroup( sgt.getGroupName())); @@ -434,7 +524,18 @@ public class EC2SoapServiceImpl implements AmazonEC2SkeletonInterface { return toDescribeSnapshotsResponse(engine.handleRequest(request)); } - + public DescribeTagsResponse describeTags(DescribeTags decsribeTags) { + EC2DescribeTags request = new EC2DescribeTags(); + DescribeTagsType dtt = decsribeTags.getDescribeTags(); + + FilterSetType fst = dtt.getFilterSet(); + + if (fst != null) + request.setFilterSet( toTagsFilterSet( fst )); + + return toDescribeTagsResponse(engine.describeTags(request)); + } + public DescribeVolumesResponse describeVolumes(DescribeVolumes describeVolumes) { EC2DescribeVolumes request = new EC2DescribeVolumes(); @@ -1094,8 +1195,28 @@ public class EC2SoapServiceImpl implements AmazonEC2SkeletonInterface { } return azfs; } - - + + private EC2TagsFilterSet toTagsFilterSet( FilterSetType fst ) { + EC2TagsFilterSet tfs = new EC2TagsFilterSet(); + + FilterType[] items = fst.getItem(); + if (items != null) { + for (FilterType item : items) { + EC2Filter oneFilter = new EC2Filter(); + String filterName = item.getName(); + oneFilter.setName( filterName ); + + ValueSetType vft = item.getValueSet(); + ValueType[] valueItems = vft.getItem(); + for (ValueType valueItem : valueItems) { + oneFilter.addValueEncoded( valueItem.getValue()); + } + tfs.addFilter( oneFilter ); + } + } + return tfs; + } + // toMethods public static DescribeVolumesResponse toDescribeVolumesResponse( EC2DescribeVolumesResponse engineResponse ) { @@ -1931,7 +2052,48 @@ public class EC2SoapServiceImpl implements AmazonEC2SkeletonInterface { response.setRevokeSecurityGroupIngressResponse( param1 ); return response; } - + + public static CreateTagsResponse toCreateTagsResponse( boolean success ) { + CreateTagsResponse response = new CreateTagsResponse(); + CreateTagsResponseType param1 = new CreateTagsResponseType(); + + param1.set_return(success); + param1.setRequestId( UUID.randomUUID().toString()); + response.setCreateTagsResponse(param1); + return response; + } + + public static DeleteTagsResponse toDeleteTagsResponse( boolean success ) { + DeleteTagsResponse response = new DeleteTagsResponse(); + DeleteTagsResponseType param1 = new DeleteTagsResponseType(); + + param1.set_return(success); + param1.setRequestId( UUID.randomUUID().toString()); + response.setDeleteTagsResponse(param1); + return response; + } + + public static DescribeTagsResponse toDescribeTagsResponse( EC2DescribeTagsResponse engineResponse) { + DescribeTagsResponse response = new DescribeTagsResponse(); + DescribeTagsResponseType param1 = new DescribeTagsResponseType(); + + EC2ResourceTag[] tags = engineResponse.getTagsSet(); + TagSetType param2 = new TagSetType(); + for (EC2ResourceTag tag : tags) { + TagSetItemType param3 = new TagSetItemType(); + param3.setResourceId(tag.getResourceId()); + param3.setResourceType(tag.getResourceType()); + param3.setKey(tag.getKey()); + if (tag.getValue() != null) + param3.setValue(tag.getValue()); + param2.addItem(param3); + } + param1.setTagSet(param2); + param1.setRequestId( UUID.randomUUID().toString()); + response.setDescribeTagsResponse(param1); + return response; + } + public DescribeKeyPairsResponse describeKeyPairs(DescribeKeyPairs describeKeyPairs) { EC2DescribeKeyPairs ec2Request = new EC2DescribeKeyPairs(); @@ -2116,10 +2278,6 @@ public class EC2SoapServiceImpl implements AmazonEC2SkeletonInterface { public CreateSubnetResponse createSubnet(CreateSubnet createSubnet) { throw new EC2ServiceException(ClientError.Unsupported, "This operation is not available"); } - - public CreateTagsResponse createTags(CreateTags createTags) { - throw new EC2ServiceException(ClientError.Unsupported, "This operation is not available"); - } public CreateVpcResponse createVpc(CreateVpc createVpc) { throw new EC2ServiceException(ClientError.Unsupported, "This operation is not available"); @@ -2156,10 +2314,6 @@ public class EC2SoapServiceImpl implements AmazonEC2SkeletonInterface { public DeleteSubnetResponse deleteSubnet(DeleteSubnet deleteSubnet) { throw new EC2ServiceException(ClientError.Unsupported, "This operation is not available"); } - - public DeleteTagsResponse deleteTags(DeleteTags deleteTags) { - throw new EC2ServiceException(ClientError.Unsupported, "This operation is not available"); - } public DeleteVpcResponse deleteVpc(DeleteVpc deleteVpc) { throw new EC2ServiceException(ClientError.Unsupported, "This operation is not available"); @@ -2229,10 +2383,6 @@ public class EC2SoapServiceImpl implements AmazonEC2SkeletonInterface { throw new EC2ServiceException(ClientError.Unsupported, "This operation is not available"); } - public DescribeTagsResponse describeTags(DescribeTags describeTags) { - throw new EC2ServiceException(ClientError.Unsupported, "This operation is not available"); - } - public DescribeVpcsResponse describeVpcs(DescribeVpcs describeVpcs) { throw new EC2ServiceException(ClientError.Unsupported, "This operation is not available"); } diff --git a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2DescribeTags.java b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2DescribeTags.java new file mode 100644 index 00000000000..1ce65277c75 --- /dev/null +++ b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2DescribeTags.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.bridge.service.core.ec2; + +public class EC2DescribeTags { + + private EC2TagsFilterSet tfs = null; + + public EC2DescribeTags() { + } + + public EC2TagsFilterSet getFilterSet() { + return tfs; + } + + public void setFilterSet( EC2TagsFilterSet param ) { + tfs = param; + } +} diff --git a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2DescribeTagsResponse.java b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2DescribeTagsResponse.java new file mode 100644 index 00000000000..9559e65a373 --- /dev/null +++ b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2DescribeTagsResponse.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.bridge.service.core.ec2; + +import java.util.ArrayList; +import java.util.List; + +public class EC2DescribeTagsResponse { + + private List tagsSet = new ArrayList(); + + public EC2DescribeTagsResponse() { + } + + public void addTags( EC2ResourceTag param ) { + tagsSet.add( param ); + } + + public EC2ResourceTag[] getTagsSet() { + return tagsSet.toArray(new EC2ResourceTag[0]); + } +} diff --git a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2Engine.java b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2Engine.java index 5f4584759f9..36d2b686909 100644 --- a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2Engine.java +++ b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2Engine.java @@ -59,6 +59,7 @@ import com.cloud.stack.models.CloudStackNic; import com.cloud.stack.models.CloudStackOsType; import com.cloud.stack.models.CloudStackPasswordData; import com.cloud.stack.models.CloudStackResourceLimit; +import com.cloud.stack.models.CloudStackResourceTag; import com.cloud.stack.models.CloudStackSecurityGroup; import com.cloud.stack.models.CloudStackSecurityGroupIngress; import com.cloud.stack.models.CloudStackServiceOffering; @@ -1311,6 +1312,80 @@ public class EC2Engine { } } + /** + * Create/Delete tags + * + * @param request + * @param operation + * @return + */ + public boolean modifyTags( EC2Tags request, String operation) { + try { + List resourceTagList = new ArrayList(); + for ( EC2TagKeyValue resourceTag : request.getResourceTags()){ + CloudStackKeyValue pair = new CloudStackKeyValue(); + pair.setKeyValue(resourceTag.getKey(), resourceTag.getValue()); + resourceTagList.add(pair); + } + EC2TagTypeId[] resourceTypeSet = request.getResourceTypeSet(); + for (EC2TagTypeId resourceType : resourceTypeSet) { + String cloudStackResourceType = mapToCloudStackResourceType(resourceType.getResourceType()); + List resourceIdList = new ArrayList(); + for ( String resourceId : resourceType.getResourceIds()) + resourceIdList.add(resourceId); + CloudStackInfoResponse resp = new CloudStackInfoResponse(); + if (operation.equalsIgnoreCase("create")) + resp = getApi().createTags(cloudStackResourceType, resourceIdList, resourceTagList); + else if(operation.equalsIgnoreCase("delete")) + resp = getApi().deleteTags(cloudStackResourceType, resourceIdList, resourceTagList); + else + throw new EC2ServiceException( ServerError.InternalError, "Unknown operation." ); + if (resp.getSuccess() == false) + return false; + } + return true; + } catch (Exception e){ + logger.error( "EC2 Create/Delete Tags - ", e); + throw new EC2ServiceException(ServerError.InternalError, e.getMessage() != null ? + e.getMessage() : "An unexpected error occurred."); + } + } + + /** + * Describe tags + * + * @param request + * @return + */ + public EC2DescribeTagsResponse describeTags (EC2DescribeTags request) { + try { + EC2DescribeTagsResponse tagResponse = new EC2DescribeTagsResponse(); + List resourceTagList = getApi().listTags(null, null, null, true, null); + + List tagList = new ArrayList(); + if (resourceTagList != null && resourceTagList.size() > 0) { + for (CloudStackResourceTag resourceTag: resourceTagList) { + EC2ResourceTag tag = new EC2ResourceTag(); + tag.setResourceId(resourceTag.getResourceId()); + tag.setResourceType(mapToAmazonResourceType(resourceTag.getResourceType())); + tag.setKey(resourceTag.getKey()); + if (resourceTag.getValue() != null) + tag.setValue(resourceTag.getValue()); + tagResponse.addTags(tag); + } + } + + EC2TagsFilterSet tfs = request.getFilterSet(); + if (tfs == null) + return tagResponse; + else + return tfs.evaluate(tagResponse); + } catch(Exception e) { + logger.error("EC2 DescribeTags - ", e); + throw new EC2ServiceException(ServerError.InternalError, e.getMessage()); + } + } + /** * Reboot an instance or instances * @@ -2245,6 +2320,36 @@ public class EC2Engine { return "error"; } + /** + * Map Amazon resourceType to CloudStack resourceType + * + * @param Amazon resourceType + * @return CloudStack resourceType + */ + private String mapToCloudStackResourceType( String resourceType) { + if (resourceType.equalsIgnoreCase("image")) + return("template"); + else if(resourceType.equalsIgnoreCase("instance")) + return("userVm"); + else + return resourceType; + } + + /** + * Map Amazon resourceType to CloudStack resourceType + * + * @param CloudStack resourceType + * @return Amazon resourceType + */ + private String mapToAmazonResourceType( String resourceType) { + if (resourceType.equalsIgnoreCase("template")) + return("image"); + else if(resourceType.equalsIgnoreCase("userVm")) + return("instance"); + else + return (resourceType.toLowerCase()); + } + /** * Stop an instance * Wait until one specific VM has stopped diff --git a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2ResourceTag.java b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2ResourceTag.java new file mode 100644 index 00000000000..e3c5881909a --- /dev/null +++ b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2ResourceTag.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.bridge.service.core.ec2; + +public class EC2ResourceTag { + private String resourceId; + private String resourceType; + private String key; + private String value; + + public EC2ResourceTag() { + resourceId = null; + resourceType = null; + key = null; + value = null; + } + + public void setResourceId( String resourceId ) { + this.resourceId = resourceId; + } + + public String getResourceId() { + return this.resourceId; + } + + public void setResourceType( String resourceType ) { + this.resourceType = resourceType; + } + + public String getResourceType() { + return this.resourceType; + } + + public void setKey( String key ) { + this.key = key; + } + + public String getKey() { + return this.key; + } + + public void setValue( String value ) { + this.value = value; + } + + public String getValue() { + return this.value; + } +} diff --git a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2TagKeyValue.java b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2TagKeyValue.java new file mode 100644 index 00000000000..beb2af9d99f --- /dev/null +++ b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2TagKeyValue.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.bridge.service.core.ec2; + +public class EC2TagKeyValue { + private String key; + private String value; + + public EC2TagKeyValue() { + key = null; + value = null; + } + + public void setKey( String key ) { + this.key = key; + } + + public String getKey() { + return this.key; + } + + public void setValue( String value ) { + this.value = value; + } + + public String getValue() { + return this.value; + } +} diff --git a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2TagTypeId.java b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2TagTypeId.java new file mode 100644 index 00000000000..3a0666edf82 --- /dev/null +++ b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2TagTypeId.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.bridge.service.core.ec2; + +import java.util.ArrayList; +import java.util.List; + +public class EC2TagTypeId { + + private String resourceType; + private List resourceIdSet = new ArrayList(); + + public EC2TagTypeId() { + resourceType = null; + } + + public void setResourceType( String resourceType ) { + this.resourceType = resourceType; + } + + public String getResourceType() { + return this.resourceType; + } + + public void addResourceId( String param ) { + resourceIdSet.add( param ); + } + + public String[] getResourceIds() { + return resourceIdSet.toArray(new String[0]); + } +} diff --git a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2Tags.java b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2Tags.java new file mode 100644 index 00000000000..80c9f8a5afc --- /dev/null +++ b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2Tags.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.bridge.service.core.ec2; + +import java.util.ArrayList; +import java.util.List; + +public class EC2Tags { + + private List resourceTypeSet = new ArrayList(); + private List resourceTagSet = new ArrayList(); + + public void addResourceType( EC2TagTypeId param ) { + resourceTypeSet.add( param ); + } + + public EC2TagTypeId[] getResourceTypeSet() { + return resourceTypeSet.toArray(new EC2TagTypeId[0]); + } + + public void addResourceTag( EC2TagKeyValue param ) { + resourceTagSet.add( param ); + } + + public EC2TagKeyValue[] getResourceTags() { + return resourceTagSet.toArray(new EC2TagKeyValue[0]); + } +} + diff --git a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2TagsFilterSet.java b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2TagsFilterSet.java new file mode 100644 index 00000000000..c2d33c39ea1 --- /dev/null +++ b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2TagsFilterSet.java @@ -0,0 +1,107 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES 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.bridge.service.core.ec2; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.log4j.Logger; + +import com.cloud.bridge.service.exception.EC2ServiceException; + +public class EC2TagsFilterSet { + protected final static Logger logger = Logger.getLogger(EC2TagsFilterSet.class); + + protected List filterSet = new ArrayList(); + + private Map filterTypes = new HashMap(); + + public EC2TagsFilterSet() { + filterTypes.put( "resource-id", "String" ); + filterTypes.put( "resource-type", "String" ); + filterTypes.put( "key", "String" ); + filterTypes.put( "value", "String" ); + } + + public void addFilter( EC2Filter param ) { + String filterName = param.getName(); + String value = (String) filterTypes.get( filterName ); + + if (null == value) + throw new EC2ServiceException( "Unsupported filter [" + filterName + "] - 1", 501 ); + + if (null != value && value.equalsIgnoreCase( "null" )) + throw new EC2ServiceException( "Unsupported filter [" + filterName + "] - 2", 501 ); + + filterSet.add( param ); + } + + public EC2Filter[] getFilterSet() { + return filterSet.toArray(new EC2Filter[0]); + } + + public EC2DescribeTagsResponse evaluate( EC2DescribeTagsResponse sampleList) throws ParseException { + EC2DescribeTagsResponse resultList = new EC2DescribeTagsResponse(); + + boolean matched; + + EC2ResourceTag[] tagSet = sampleList.getTagsSet(); + EC2Filter[] filterSet = getFilterSet(); + for (EC2ResourceTag tag : tagSet) { + matched = true; + for (EC2Filter filter : filterSet) { + if (!filterMatched(tag, filter)) { + matched = false; + break; + } + } + if (matched == true) + resultList.addTags(tag); + } + return resultList; + } + + private boolean filterMatched( EC2ResourceTag tag, EC2Filter filter ) throws ParseException { + String filterName = filter.getName(); + String[] valueSet = filter.getValueSet(); + + if ( filterName.equalsIgnoreCase("resource-id")) { + return containsString(tag.getResourceId(), valueSet); + } else if ( filterName.equalsIgnoreCase("resource-type")) { + return containsString(tag.getResourceType(), valueSet); + } else if ( filterName.equalsIgnoreCase("key")) { + return containsString(tag.getKey(), valueSet); + } else if ( filterName.equalsIgnoreCase("value")) { + return containsString(tag.getValue(), valueSet); + } else + return false; + } + + private boolean containsString( String lookingFor, String[] set ){ + if (lookingFor == null) + return false; + + for (String filter: set) { + if (lookingFor.matches( filter )) return true; + } + return false; + } +} diff --git a/awsapi/src/com/cloud/stack/CloudStackApi.java b/awsapi/src/com/cloud/stack/CloudStackApi.java index e46ebddb546..50b2b25af40 100644 --- a/awsapi/src/com/cloud/stack/CloudStackApi.java +++ b/awsapi/src/com/cloud/stack/CloudStackApi.java @@ -42,6 +42,7 @@ import com.cloud.stack.models.CloudStackOsType; import com.cloud.stack.models.CloudStackPasswordData; import com.cloud.stack.models.CloudStackPortForwardingRule; import com.cloud.stack.models.CloudStackResourceLimit; +import com.cloud.stack.models.CloudStackResourceTag; import com.cloud.stack.models.CloudStackSecurityGroup; import com.cloud.stack.models.CloudStackSecurityGroupIngress; import com.cloud.stack.models.CloudStackServiceOffering; @@ -973,7 +974,81 @@ public class CloudStackApi { } return _client.call(cmd, apiKey, secretKey, true, ApiConstants.EXTRACT_VOLUME_RESPONSE, ApiConstants.VOLUME, CloudStackExtractTemplate.class); } - + + //Tags + /** + * Create tags + * + * @param resource type + * @param resource id's + * @param tags + * @return + * @throws Exception + * + */ + public CloudStackInfoResponse createTags(String resourceType, ListresourceIds, + List resourceTags) throws Exception { + CloudStackCommand cmd = new CloudStackCommand(ApiConstants.CREATE_TAGS); + cmd = setParams(cmd, resourceType, resourceIds, resourceTags); + return _client.call(cmd, apiKey, secretKey, true, ApiConstants.CREATE_TAGS_RESPONSE, + null, CloudStackInfoResponse.class); + } + + /** + * Delete tags + * + * @param resource type + * @param resource id's + * @param tags + * @return + * @throws Exception + * + */ + public CloudStackInfoResponse deleteTags(String resourceType, ListresourceIds, + List resourceTags) throws Exception { + CloudStackCommand cmd = new CloudStackCommand(ApiConstants.DELETE_TAGS); + cmd = setParams(cmd, resourceType, resourceIds, resourceTags); + return _client.call(cmd, apiKey, secretKey, true, ApiConstants.DELETE_TAGS_RESPONSE, + null, CloudStackInfoResponse.class); + } + + public List listTags(String account, String domainId, + Boolean isRecursive, Boolean listAll, String keyWord) throws Exception { + CloudStackCommand cmd = new CloudStackCommand(ApiConstants.LIST_TAGS); + if (cmd != null) { + if (account != null) cmd.setParam(ApiConstants.ACCOUNT, account); + if (domainId != null) cmd.setParam(ApiConstants.DOMAIN_ID, domainId); + if (isRecursive != null) cmd.setParam(ApiConstants.IS_RECURSIVE, isRecursive.toString()); + if (listAll != null) cmd.setParam(ApiConstants.LIST_ALL, listAll.toString()); + if (keyWord != null) cmd.setParam(ApiConstants.KEYWORD, keyWord); + } + return _client.listCall(cmd, apiKey, secretKey, ApiConstants.LIST_TAGS_RESPONSE, + ApiConstants.TAG , new TypeToken>() {}.getType()); + } + + private CloudStackCommand setParams(CloudStackCommand cmd, String resourceType, ListresourceIds, + List resourceTags) { + if (cmd != null) { + cmd.setParam(ApiConstants.RESOURCE_TYPE, resourceType); + if (resourceIds != null && resourceIds.size() > 0) { + String resourceIdList = resourceIds.get(0); + for (int i=1 ; i 0) { + int i=0; + for (CloudStackKeyValue resourceTag : resourceTags) { + cmd.setParam(ApiConstants.TAGS+"["+i+"].key", resourceTag.getKey()); + if (resourceTag.getValue() != null) + cmd.setParam(ApiConstants.TAGS+"["+i+"].value", resourceTag.getValue()); + i++; + } + } + } + return cmd; + } + // Security Groups /** * Creates a security group diff --git a/awsapi/src/com/cloud/stack/models/ApiConstants.java b/awsapi/src/com/cloud/stack/models/ApiConstants.java index e00a9b90859..2097ab6f135 100644 --- a/awsapi/src/com/cloud/stack/models/ApiConstants.java +++ b/awsapi/src/com/cloud/stack/models/ApiConstants.java @@ -85,6 +85,8 @@ public class ApiConstants { public static final String CREATE_SNAPSHOT_RESPONSE = "createsnapshotresponse"; public static final String CREATE_SSH_KEY_PAIR = "createSSHKeyPair"; public static final String CREATE_SSH_KEY_PAIR_RESPONSE = "createsshkeypairresponse"; + public static final String CREATE_TAGS = "createTags"; + public static final String CREATE_TAGS_RESPONSE = "createtagsresponse"; public static final String CREATE_TEMPLATE = "createTemplate"; public static final String CREATE_TEMPLATE_RESPONSE = "createtemplateresponse"; public static final String CREATE_VOLUME = "createVolume"; @@ -114,6 +116,8 @@ public class ApiConstants { public static final String DELETE_SNAPSHOT_RESPONSE = "deletesnapshotresponse"; public static final String DELETE_SSH_KEY_PAIR = "deleteSSHKeyPair"; public static final String DELETE_SSH_KEY_PAIR_RESPONSE = "deletesshkeypairresponse"; + public static final String DELETE_TAGS = "deleteTags"; + public static final String DELETE_TAGS_RESPONSE = "deletetagsresponse"; public static final String DELETE_TEMPLATE = "deleteTemplate"; public static final String DELETE_TEMPLATE_RESPONSE = "deletetemplateresponse"; public static final String DELETE_VOLUME = "deleteVolume"; @@ -228,6 +232,7 @@ public class ApiConstants { public static final String ISOLATION_URI = "isolationuri"; public static final String JOB_ID = "jobid"; public static final String JOB_STATUS = "jobstatus"; + public static final String KEY = "key"; public static final String KEY_PAIR = "keypair"; public static final String KEYWORD = "keyword"; public static final String LASTNAME = "lastname"; @@ -290,6 +295,8 @@ public class ApiConstants { public static final String LIST_SSH_KEY_PAIRS = "listSSHKeyPairs"; public static final String LIST_SSH_KEY_PAIRS_RESPONSE = "listsshkeypairsresponse"; public static final String LIST_TEMPLATE_PERMISSIONS = "listTemplatePermissions"; + public static final String LIST_TAGS = "listTags"; + public static final String LIST_TAGS_RESPONSE = "listtagsresponse"; public static final String LIST_TEMPLATE_PERMISSIONS_RESPONSE = "listtemplatepermissionsresponse"; public static final String LIST_TEMPLATES = "listTemplates"; public static final String LIST_TEMPLATES_RESPONSE = "listtemplatesresponse"; @@ -380,6 +387,8 @@ public class ApiConstants { public static final String REQUIRES_HVM = "requireshvm"; public static final String RESET_PASSWORD_FOR_VIRTUAL_MACHINE = "resetPasswordForVirtualMachine"; public static final String RESET_PASSWORD_FOR_VIRTUAL_MACHINE_RESPONSE = "resetpasswordforvirtualmachineresponse"; + public static final String RESOURCE_ID = "resourceid"; + public static final String RESOURCE_IDS = "resourceIds"; public static final String RESOURCE_LIMIT = "resourcelimit"; public static final String RESOURCE_TYPE = "resourcetype"; public static final String RESTART_NETWORK = "restartNetwork"; @@ -433,6 +442,7 @@ public class ApiConstants { public static final String STORAGE_TYPE = "storagetype"; public static final String SUCCESS = "success"; public static final String SYSTEM_VM_TYPE = "systemvmtype"; + public static final String TAG = "tag"; public static final String TAGS = "tags"; public static final String TARGET_IQN = "targetiqn"; public static final String TEMPLATE = "template"; diff --git a/awsapi/src/com/cloud/stack/models/CloudStackResourceTag.java b/awsapi/src/com/cloud/stack/models/CloudStackResourceTag.java new file mode 100644 index 00000000000..8dd75c5a41b --- /dev/null +++ b/awsapi/src/com/cloud/stack/models/CloudStackResourceTag.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.stack.models; + +import com.google.gson.annotations.SerializedName; + +public class CloudStackResourceTag { + @SerializedName(ApiConstants.RESOURCE_ID) + private String resourceId; + @SerializedName(ApiConstants.RESOURCE_TYPE) + private String resourceType; + @SerializedName(ApiConstants.KEY) + private String key; + @SerializedName(ApiConstants.VALUE) + private String value; + + public CloudStackResourceTag() { + } + + public String getResourceId() { + return resourceId; + } + + public String getResourceType() { + return resourceType; + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } +} From 88f7872b81c824d2a547d6ca03b95261188c2857 Mon Sep 17 00:00:00 2001 From: Koushik Das Date: Mon, 23 Jul 2012 14:46:56 -0700 Subject: [PATCH 03/11] CS-15279 Issue happens when ROOT volume gets created and there is subsequent failure in starting the VM. During retry if allocator assigns a different storage pool the scenario was not handled. Now in case of local storage the volume get recreated on the newly assigned pool and old one gets cleaned up. In case of shared storage the existing volume is migrated to new storage pool. --- .../com/cloud/storage/StorageManagerImpl.java | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/server/src/com/cloud/storage/StorageManagerImpl.java b/server/src/com/cloud/storage/StorageManagerImpl.java index 27d8cb71e83..d94bada2866 100755 --- a/server/src/com/cloud/storage/StorageManagerImpl.java +++ b/server/src/com/cloud/storage/StorageManagerImpl.java @@ -3210,10 +3210,25 @@ public class StorageManagerImpl implements StorageManager, Manager, ClusterManag } else { if (assignedPool.getId() != vol.getPoolId()) { if (s_logger.isDebugEnabled()) { - s_logger.debug("Volume " + vol + " is not recreatable! Cannot recreate on storagepool: " + assignedPool); + s_logger.debug("Mismatch in storage pool " + assignedPool + " assigned by deploymentPlanner and the one associated with volume " + vol); + } + if (vm.getServiceOffering().getUseLocalStorage()) + { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Local volume " + vol + " will be recreated on storage pool " + assignedPool + " assigned by deploymentPlanner"); + } + recreateVols.add(vol); + } else { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Shared volume " + vol + " will be migrated on storage pool " + assignedPool + " assigned by deploymentPlanner"); + } + try { + Volume migratedVol = migrateVolume(vol.getId(), assignedPool.getId()); + vm.addDisk(new VolumeTO(migratedVol, assignedPool)); + } catch (ConcurrentOperationException e) { + throw new StorageUnavailableException("Volume migration failed for " + vol, Volume.class, vol.getId()); + } } - throw new StorageUnavailableException("Volume is not recreatable, Unable to create " + vol, Volume.class, vol.getId()); - // copy volume usecase - not yet developed. } else { StoragePoolVO pool = _storagePoolDao.findById(vol.getPoolId()); vm.addDisk(new VolumeTO(vol, pool)); From 5e94b0d12e50c6ba3a7defd00aeb04c79286f14f Mon Sep 17 00:00:00 2001 From: bfederle Date: Mon, 23 Jul 2012 13:26:52 -0700 Subject: [PATCH 04/11] Implement tag UI widget Create UI for handling new tag API. This currently supports the detail view and multi-edit To enable tags UI, add a 'tags' object to each detailView/multiEdit configuration: tabs: { ... details: { ... tags: { actions: { add: function(args) { setTimeout(function() { args.response.success({ notification: { desc: 'Add tags for instance', poll: testData.notifications.testPoll } }); }, 500); }, remove: function(args) { args.response.success({ notification: { desc: 'Remove tags for instance', poll: testData.notifications.testPoll } }); } }, dataProvider: function(args) { args.response.success({ data: [ { id: '1', key: 'user', value: 'brian' }, { id: '2', key: 'region', value: 'usa' } ] }); } } ... Conflicts: ui/css/cloudstack3.css ui/scripts/ui/widgets/tagger.js --- ui/css/cloudstack3.css | 198 ++++++++++++++++++++++++++++ ui/scripts/ui/widgets/detailView.js | 48 ++++++- ui/scripts/ui/widgets/multiEdit.js | 12 +- ui/scripts/ui/widgets/tagger.js | 197 +++++++++++++++++++++++++++ 4 files changed, 446 insertions(+), 9 deletions(-) create mode 100644 ui/scripts/ui/widgets/tagger.js diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css index ef81697e464..e405fbf4fcb 100644 --- a/ui/css/cloudstack3.css +++ b/ui/css/cloudstack3.css @@ -8952,6 +8952,204 @@ div.panel.ui-dialog div.list-view div.fixed-header { background: #DFE1E3; } +/*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: 179px; + 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: 28px; + 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: 42px; + top: 29px; + /*[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[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; +} + +.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 { + font-size: 10px; + position: relative; + left: 15px; + top: -2px; +} + +.tagger.readonly ul li span.label { + left: 6px; +} + +.tagger ul li span.remove { + width: 15px !important; + overflow: hidden !important; + height: 11px !important; + background: #DFDFDF url(../images/sprites.png) no-repeat -596px -1183px; + 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 { +} + +.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%; diff --git a/ui/scripts/ui/widgets/detailView.js b/ui/scripts/ui/widgets/detailView.js index b1e9a68c8a9..086f66d3e23 100644 --- a/ui/scripts/ui/widgets/detailView.js +++ b/ui/scripts/ui/widgets/detailView.js @@ -287,10 +287,14 @@ * @param callback */ edit: function($detailView, args) { + $detailView.addClass('edit-mode'); + if ($detailView.find('.button.done').size()) return false; // Convert value TDs - var $inputs = $detailView.find('input, select'); + var $inputs = $detailView.find('input, select').filter(function() { + return !$(this).closest('.tagger').size() && !$(this).attr('type') == 'submit'; + }); var action = args.actions[args.actionName]; var id = $detailView.data('view-args').id; var $editButton = $('
').addClass('button done').html(_l('label.apply')).hide(); @@ -302,9 +306,14 @@ $detailView.find('.ui-tabs-panel .detail-group.actions') ).fadeIn(); + $detailView.find('.tagger').removeClass('readonly'); + $detailView.find('.tagger').find('input[type=text]').val(''); + var convertInputs = function($inputs) { // Save and turn back into labels $inputs.each(function() { + if ($(this).closest('.tagger').size()) return true; + var $input = $(this); var $value = $input.closest('td.value span'); @@ -328,8 +337,12 @@ }; var removeEditForm = function() { + $detailView.removeClass('edit-mode'); + // Remove Edit form - var $form = $detailView.find('form'); + var $form = $detailView.find('form').filter(function() { + return !$(this).closest('.tagger').size(); + }); if ($form.size()) { var $mainGroups = $form.find('div.main-groups').detach(); $form.parent('div').append($mainGroups); @@ -337,11 +350,15 @@ } //Remove required labels $detailView.find('span.field-required').remove(); - } + $detailView.find('.tagger').addClass('readonly'); + + }; // Put in original values var cancelEdits = function($inputs, $editButton) { $inputs.each(function() { + if ($(this).closest('.tagger').size()) return true; + var $input = $(this); var $value = $input.closest('td.value span'); var originalValue = $input.data('original-value'); @@ -424,8 +441,12 @@ }; $editButton.click(function() { - var $inputs = $detailView.find('input, select'), - $form = $detailView.find('form'); + var $inputs = $detailView.find('input, select').filter(function() { + return !$(this).closest('.tagger').size(); + }); + var $form = $detailView.find('form').filter(function() { + return !$(this).closest('.tagger').size(); + }); if ($(this).hasClass('done')) { if (!$form.valid()) { @@ -438,6 +459,8 @@ } else { // Cancel cancelEdits($inputs, $editButton); } + + return true; }); $detailView.find('td.value span').each(function() { @@ -511,8 +534,11 @@ } // Setup form validation - $detailView.find('form').validate(); - $detailView.find('form').find('input, select').each(function() { + var $form = $detailView.find('form').filter(function() { + return !$(this).closest('.tagger').size(); + }); + $form.validate(); + $form.find('input, select').each(function() { var data = $(this).parent('span').data('validation-rules'); if (data) { $(this).rules('add', data); @@ -934,6 +960,14 @@ actionFilter: actionFilter }).appendTo($tabContent); + if (tabs.tags) { + $('
').tagger( + $.extend(true, {}, tabs.tags, { + context: $detailView.data('view-args').context + }) + ).appendTo($tabContent).addClass('readonly'); + } + return true; }, error: function() { diff --git a/ui/scripts/ui/widgets/multiEdit.js b/ui/scripts/ui/widgets/multiEdit.js index 64d94f6dd90..89d260300eb 100644 --- a/ui/scripts/ui/widgets/multiEdit.js +++ b/ui/scripts/ui/widgets/multiEdit.js @@ -332,10 +332,16 @@ after: function(args) { var $loading = $('
').addClass('loading-overlay').prependTo($dataItem); performAction({ data: args.data, complete: function() { - $multi.multiEdit('refresh'); + $multi.trigger('refresh'); } }); } }); + + if (options.tags) { + $(':ui-dialog').append( + $('
').addClass('multi-edit-tags').tagger(options.tags) + ); + } } }) ); @@ -652,6 +658,7 @@ $.fn.multiEdit = function(args) { var dataProvider = args.dataProvider; var multipleAdd = args.multipleAdd; + var tags = args.tags; var $multi = $('
').addClass('multi-edit').appendTo(this); var $multiForm = $('
').appendTo($multi); var $inputTable = $('').addClass('multi-edit').appendTo($multiForm); @@ -918,7 +925,8 @@ context: $.extend(true, {}, context, this._context), ignoreEmptyFields: ignoreEmptyFields, preFilter: actionPreFilter, - listView: listView + listView: listView, + tags: tags } ).appendTo($dataBody); }); diff --git a/ui/scripts/ui/widgets/tagger.js b/ui/scripts/ui/widgets/tagger.js new file mode 100644 index 00000000000..b6d2f0a0dea --- /dev/null +++ b/ui/scripts/ui/widgets/tagger.js @@ -0,0 +1,197 @@ +(function($, cloudStack) { + var elems = { + inputArea: function(args) { + var $form = $('').addClass('tag-input'); + var $keyField = $('
').addClass('field key'); + var $keyLabel = $('