From 977051b7ebd9da2b5259193268b04571e30a0760 Mon Sep 17 00:00:00 2001 From: 956081712 <956081712@qq.com> Date: Sat, 29 Feb 2020 02:51:30 +0800 Subject: [PATCH] add findImage and ScalingActivitie Controller (#4365) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .../clouddriver-alicloud.gradle | 5 +- .../controllers/AliCloudImageController.java | 175 ++++++++++++++++++ .../AliCloudScalingActivitiesController.java | 98 ++++++++++ .../AliCloudImageControllerTest.java | 93 ++++++++++ ...iCloudScalingActivitiesControllerTest.java | 89 +++++++++ 5 files changed, 459 insertions(+), 1 deletion(-) create mode 100644 clouddriver-alicloud/src/main/java/com/netflix/spinnaker/clouddriver/alicloud/controllers/AliCloudImageController.java create mode 100644 clouddriver-alicloud/src/main/java/com/netflix/spinnaker/clouddriver/alicloud/controllers/AliCloudScalingActivitiesController.java create mode 100644 clouddriver-alicloud/src/test/java/com/netflix/spinnaker/clouddriver/alicloud/controllers/AliCloudImageControllerTest.java create mode 100644 clouddriver-alicloud/src/test/java/com/netflix/spinnaker/clouddriver/alicloud/controllers/AliCloudScalingActivitiesControllerTest.java diff --git a/clouddriver-alicloud/clouddriver-alicloud.gradle b/clouddriver-alicloud/clouddriver-alicloud.gradle index 295778a2c70..6aacbde11d4 100644 --- a/clouddriver-alicloud/clouddriver-alicloud.gradle +++ b/clouddriver-alicloud/clouddriver-alicloud.gradle @@ -16,6 +16,7 @@ dependencies { implementation "org.apache.httpcomponents:httpcore" implementation "com.github.ben-manes.caffeine:guava" implementation "com.netflix.spinnaker.moniker:moniker" + implementation "javax.servlet:javax.servlet-api" implementation 'com.aestasit.infrastructure.sshoogr:sshoogr:0.9.25' implementation 'com.jcraft:jsch.agentproxy.jsch:0.0.9' @@ -24,7 +25,9 @@ dependencies { implementation 'com.aliyun:aliyun-java-sdk-slb:3.2.9' implementation 'com.aliyun:aliyun-java-sdk-vpc:3.0.6' implementation 'com.aliyun:aliyun-java-sdk-ecs:4.16.10' - implementation 'com.aliyun:aliyun-java-sdk-ess:2.3.0' + implementation 'com.aliyun:aliyun-java-sdk-ess:2.3.2' + implementation "org.springframework.boot:spring-boot-actuator" + implementation "org.springframework.boot:spring-boot-starter-web" testImplementation "cglib:cglib-nodep" testImplementation "org.mockito:mockito-core" diff --git a/clouddriver-alicloud/src/main/java/com/netflix/spinnaker/clouddriver/alicloud/controllers/AliCloudImageController.java b/clouddriver-alicloud/src/main/java/com/netflix/spinnaker/clouddriver/alicloud/controllers/AliCloudImageController.java new file mode 100644 index 00000000000..41959022de5 --- /dev/null +++ b/clouddriver-alicloud/src/main/java/com/netflix/spinnaker/clouddriver/alicloud/controllers/AliCloudImageController.java @@ -0,0 +1,175 @@ +/* + * Copyright 2019 Alibaba Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.clouddriver.alicloud.controllers; + +import static com.netflix.spinnaker.clouddriver.core.provider.agent.Namespace.IMAGES; +import static com.netflix.spinnaker.clouddriver.core.provider.agent.Namespace.NAMED_IMAGES; + +import com.netflix.spinnaker.cats.cache.Cache; +import com.netflix.spinnaker.cats.cache.CacheData; +import com.netflix.spinnaker.clouddriver.alicloud.cache.Keys; +import com.netflix.spinnaker.kork.web.exceptions.InvalidRequestException; +import groovy.util.logging.Slf4j; +import java.util.*; +import javax.servlet.http.HttpServletRequest; +import lombok.Data; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@RequestMapping("/alicloud/images") +public class AliCloudImageController { + + private final Cache cacheView; + + @Autowired + public AliCloudImageController(Cache cacheView) { + this.cacheView = cacheView; + } + + @RequestMapping(value = "/find", method = RequestMethod.GET) + List list(LookupOptions lookupOptions, HttpServletRequest request) { + String glob = lookupOptions.getQ(); + if (StringUtils.isAllBlank(glob) && glob.length() < 3) { + throw new InvalidRequestException("Lost search condition or length less 3"); + } + glob = "*" + glob + "*"; + String imageSearchKey = + Keys.getImageKey( + glob, + StringUtils.isAllBlank(lookupOptions.account) ? "*" : lookupOptions.account, + StringUtils.isAllBlank(lookupOptions.region) ? "*" : lookupOptions.region); + Collection imageIdentifiers = cacheView.filterIdentifiers(IMAGES.ns, imageSearchKey); + Collection images = cacheView.getAll(IMAGES.ns, imageIdentifiers, null); + String nameKey = + Keys.getNamedImageKey( + StringUtils.isAllBlank(lookupOptions.account) ? "*" : lookupOptions.account, glob); + Collection nameImageIdentifiers = cacheView.filterIdentifiers(NAMED_IMAGES.ns, nameKey); + Collection nameImages = + cacheView.getAll(NAMED_IMAGES.ns, nameImageIdentifiers, null); + return filter(render(nameImages, images), extractTagFilters(request)); + } + + private static List filter(List namedImages, Map tagFilters) { + if (tagFilters.isEmpty()) { + return namedImages; + } + List filter = new ArrayList<>(); + for (Image namedImage : namedImages) { + if (checkInclude(namedImage, tagFilters)) { + filter.add(namedImage); + } + } + return filter; + } + + /* private static boolean checkInclude(Image image, Map tagFilters) { + boolean flag = false; + List tags = (List) image.getAttributes().get("tags"); + if (tags != null) { + for (Map tag : tags) { + String tagKey = tag.get("tagKey").toString(); + String tagValue = tag.get("tagValue").toString(); + if (StringUtils.isNotEmpty(tagFilters.get(tagKey)) + && tagFilters.get(tagKey).equalsIgnoreCase(tagValue)) { + flag = true; + } else { + flag = false; + break; + } + } + } + return flag; + }*/ + + private static boolean checkInclude(Image image, Map tagFilters) { + boolean flag = false; + List tags = (List) image.getAttributes().get("tags"); + Map imageMap = new HashMap<>(tags.size()); + for (Map tag : tags) { + imageMap.put(tag.get("tagKey").toString(), tag.get("tagValue").toString()); + } + for (Map.Entry entry : tagFilters.entrySet()) { + String tagKey = entry.getKey(); + String tagValue = entry.getValue(); + if (StringUtils.isNotEmpty(imageMap.get(tagKey)) + && imageMap.get(tagKey).equalsIgnoreCase(tagValue)) { + flag = true; + } else { + flag = false; + break; + } + } + return flag; + } + + private List render(Collection namedImages, Collection images) { + List list = new ArrayList<>(); + for (CacheData image : images) { + Map attributes = image.getAttributes(); + list.add(new Image(String.valueOf(attributes.get("imageName")), attributes)); + } + + for (CacheData nameImage : namedImages) { + Map attributes = nameImage.getAttributes(); + list.add(new Image(String.valueOf(attributes.get("imageName")), attributes)); + } + return list; + } + + private static Map extractTagFilters(HttpServletRequest request) { + Map parameters = new HashMap<>(16); + Enumeration parameterNames = request.getParameterNames(); + while (parameterNames.hasMoreElements()) { + String parameterName = parameterNames.nextElement(); + if (parameterName.toLowerCase().startsWith("tag:")) { + parameters.put( + parameterName.replaceAll("tag:", "").toLowerCase(), + request.getParameter(parameterName)); + } + } + return parameters; + } + + @Data + public static class Image { + public Image(String imageName, Map attributes) { + this.imageName = imageName; + this.attributes = attributes; + } + + String imageName; + Map attributes; + } + + @Data + static class LookupOptions { + String q; + String account; + String region; + } + + @Data + static class Tag { + String tagKey; + String tagValue; + } +} diff --git a/clouddriver-alicloud/src/main/java/com/netflix/spinnaker/clouddriver/alicloud/controllers/AliCloudScalingActivitiesController.java b/clouddriver-alicloud/src/main/java/com/netflix/spinnaker/clouddriver/alicloud/controllers/AliCloudScalingActivitiesController.java new file mode 100644 index 00000000000..f305f43934b --- /dev/null +++ b/clouddriver-alicloud/src/main/java/com/netflix/spinnaker/clouddriver/alicloud/controllers/AliCloudScalingActivitiesController.java @@ -0,0 +1,98 @@ +/* + * Copyright 2019 Alibaba Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.clouddriver.alicloud.controllers; + +import com.aliyuncs.IAcsClient; +import com.aliyuncs.ess.model.v20140828.DescribeScalingActivitiesRequest; +import com.aliyuncs.ess.model.v20140828.DescribeScalingActivitiesResponse; +import com.aliyuncs.ess.model.v20140828.DescribeScalingActivitiesResponse.ScalingActivity; +import com.aliyuncs.ess.model.v20140828.DescribeScalingGroupsRequest; +import com.aliyuncs.ess.model.v20140828.DescribeScalingGroupsResponse; +import com.aliyuncs.ess.model.v20140828.DescribeScalingGroupsResponse.ScalingGroup; +import com.aliyuncs.exceptions.ClientException; +import com.aliyuncs.exceptions.ServerException; +import com.netflix.spinnaker.clouddriver.alicloud.common.ClientFactory; +import com.netflix.spinnaker.clouddriver.alicloud.security.AliCloudCredentials; +import com.netflix.spinnaker.clouddriver.security.AccountCredentials; +import com.netflix.spinnaker.clouddriver.security.AccountCredentialsProvider; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping( + "/applications/{application}/clusters/{account}/{clusterName}/alicloud/serverGroups/{serverGroupName}") +public class AliCloudScalingActivitiesController { + + private final AccountCredentialsProvider accountCredentialsProvider; + + private final ClientFactory clientFactory; + + @Autowired + public AliCloudScalingActivitiesController( + AccountCredentialsProvider accountCredentialsProvider, ClientFactory clientFactory) { + this.accountCredentialsProvider = accountCredentialsProvider; + this.clientFactory = clientFactory; + } + + @RequestMapping(value = "/scalingActivities", method = RequestMethod.GET) + ResponseEntity getScalingActivities( + @PathVariable String account, + @PathVariable String serverGroupName, + @RequestParam(value = "region", required = true) String region) { + List resultList = new ArrayList<>(); + AccountCredentials credentials = accountCredentialsProvider.getCredentials(account); + if (!(credentials instanceof AliCloudCredentials)) { + Map messageMap = new HashMap<>(); + messageMap.put("message", "bad credentials"); + return new ResponseEntity(messageMap, HttpStatus.BAD_REQUEST); + } + AliCloudCredentials aliCloudCredentials = (AliCloudCredentials) credentials; + IAcsClient client = + clientFactory.createClient( + region, aliCloudCredentials.getAccessKeyId(), aliCloudCredentials.getAccessSecretKey()); + DescribeScalingGroupsRequest describeScalingGroupsRequest = new DescribeScalingGroupsRequest(); + describeScalingGroupsRequest.setScalingGroupName(serverGroupName); + describeScalingGroupsRequest.setPageSize(50); + DescribeScalingGroupsResponse describeScalingGroupsResponse; + try { + describeScalingGroupsResponse = client.getAcsResponse(describeScalingGroupsRequest); + if (describeScalingGroupsResponse.getScalingGroups().size() > 0) { + ScalingGroup scalingGroup = describeScalingGroupsResponse.getScalingGroups().get(0); + DescribeScalingActivitiesRequest activitiesRequest = new DescribeScalingActivitiesRequest(); + activitiesRequest.setScalingGroupId(scalingGroup.getScalingGroupId()); + activitiesRequest.setPageSize(50); + DescribeScalingActivitiesResponse activitiesResponse = + client.getAcsResponse(activitiesRequest); + resultList.addAll(activitiesResponse.getScalingActivities()); + } + + } catch (ServerException e) { + e.printStackTrace(); + throw new IllegalStateException(e.getMessage()); + } catch (ClientException e) { + e.printStackTrace(); + throw new IllegalStateException(e.getMessage()); + } + return new ResponseEntity(resultList, HttpStatus.OK); + } +} diff --git a/clouddriver-alicloud/src/test/java/com/netflix/spinnaker/clouddriver/alicloud/controllers/AliCloudImageControllerTest.java b/clouddriver-alicloud/src/test/java/com/netflix/spinnaker/clouddriver/alicloud/controllers/AliCloudImageControllerTest.java new file mode 100644 index 00000000000..cdb264d9cfa --- /dev/null +++ b/clouddriver-alicloud/src/test/java/com/netflix/spinnaker/clouddriver/alicloud/controllers/AliCloudImageControllerTest.java @@ -0,0 +1,93 @@ +/* + * Copyright 2019 Alibaba Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.netflix.spinnaker.clouddriver.alicloud.controllers; + +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.netflix.spinnaker.cats.cache.Cache; +import com.netflix.spinnaker.cats.cache.CacheData; +import com.netflix.spinnaker.cats.cache.DefaultCacheData; +import com.netflix.spinnaker.clouddriver.alicloud.controllers.AliCloudImageController.Image; +import com.netflix.spinnaker.clouddriver.alicloud.controllers.AliCloudImageController.LookupOptions; +import java.util.*; +import javax.servlet.http.HttpServletRequest; +import org.junit.Before; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +public class AliCloudImageControllerTest { + + private final String ACCOUNT = "test-account"; + private final String REGION = "cn-test"; + + final Cache cacheView = mock(Cache.class); + final LookupOptions lookupOptions = mock(LookupOptions.class); + final HttpServletRequest request = mock(HttpServletRequest.class); + + @Before + public void testBefore() { + when(cacheView.filterIdentifiers(anyString(), anyString())).thenAnswer(new FilterAnswer()); + when(cacheView.getAll(anyString(), any(), any())).thenAnswer(new CacheDataAnswer()); + when(lookupOptions.getQ()).thenReturn("test"); + when(request.getParameterNames()).thenAnswer(new RequestAnswer()); + } + + @Test + public void testList() { + AliCloudImageController controller = new AliCloudImageController(cacheView); + List list = controller.list(lookupOptions, request); + assertTrue(list.size() == 2); + } + + private class FilterAnswer implements Answer> { + @Override + public List answer(InvocationOnMock invocation) throws Throwable { + List list = new ArrayList<>(); + list.add("alicloud:images:ali-account:cn-hangzhou:win_xxx_xxx_xxx.vhd"); + return list; + } + } + + private class CacheDataAnswer implements Answer> { + @Override + public List answer(InvocationOnMock invocation) throws Throwable { + List cacheDatas = new ArrayList<>(); + Map attributes = new HashMap<>(); + attributes.put("account", ACCOUNT); + attributes.put("regionId", REGION); + attributes.put("imageName", "win_xxx_xxx_xxx.vhd"); + CacheData cacheData1 = + new DefaultCacheData( + "alicloud:images:ali-account:cn-hangzhou:win_xxx_xxx_xxx.vhd", attributes, null); + cacheDatas.add(cacheData1); + return cacheDatas; + } + } + + private class RequestAnswer implements Answer> { + @Override + public Enumeration answer(InvocationOnMock invocation) throws Throwable { + List list = new ArrayList<>(); + Enumeration enumeration = Collections.enumeration(list); + return enumeration; + } + } +} diff --git a/clouddriver-alicloud/src/test/java/com/netflix/spinnaker/clouddriver/alicloud/controllers/AliCloudScalingActivitiesControllerTest.java b/clouddriver-alicloud/src/test/java/com/netflix/spinnaker/clouddriver/alicloud/controllers/AliCloudScalingActivitiesControllerTest.java new file mode 100644 index 00000000000..010e94736b0 --- /dev/null +++ b/clouddriver-alicloud/src/test/java/com/netflix/spinnaker/clouddriver/alicloud/controllers/AliCloudScalingActivitiesControllerTest.java @@ -0,0 +1,89 @@ +/* + * Copyright 2019 Alibaba Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.netflix.spinnaker.clouddriver.alicloud.controllers; + +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.aliyuncs.ess.model.v20140828.DescribeScalingActivitiesResponse; +import com.aliyuncs.ess.model.v20140828.DescribeScalingActivitiesResponse.ScalingActivity; +import com.aliyuncs.ess.model.v20140828.DescribeScalingGroupsResponse; +import com.aliyuncs.ess.model.v20140828.DescribeScalingGroupsResponse.ScalingGroup; +import com.aliyuncs.exceptions.ClientException; +import com.netflix.spinnaker.clouddriver.alicloud.deploy.ops.CommonAtomicOperation; +import com.netflix.spinnaker.clouddriver.security.AccountCredentialsProvider; +import java.util.ArrayList; +import java.util.List; +import org.junit.Before; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.springframework.http.ResponseEntity; + +public class AliCloudScalingActivitiesControllerTest extends CommonAtomicOperation { + + private final String SERVERGROUPNAME = "test-serverGroupName"; + + static AccountCredentialsProvider accountCredentialsProvider = + mock(AccountCredentialsProvider.class); + + @Before + public void testBefore() throws ClientException { + when(accountCredentialsProvider.getCredentials(anyString())).thenReturn(credentials); + when(client.getAcsResponse(any())) + .thenAnswer(new DescribeScalingGroupsAnswer()) + .thenAnswer(new DescribeScalingActivitiesAnswer()); + } + + @Test + public void testGetScalingActivities() { + AliCloudScalingActivitiesController controller = + new AliCloudScalingActivitiesController(accountCredentialsProvider, clientFactory); + ResponseEntity scalingActivities = + controller.getScalingActivities(ACCOUNT, SERVERGROUPNAME, REGION); + assertTrue(scalingActivities != null); + } + + private class DescribeScalingGroupsAnswer implements Answer { + @Override + public DescribeScalingGroupsResponse answer(InvocationOnMock invocation) throws Throwable { + DescribeScalingGroupsResponse response = new DescribeScalingGroupsResponse(); + List scalingGroups = new ArrayList<>(); + ScalingGroup scalingGroup = new ScalingGroup(); + scalingGroup.setScalingGroupId("test-ID"); + scalingGroups.add(scalingGroup); + response.setScalingGroups(scalingGroups); + return response; + } + } + + private class DescribeScalingActivitiesAnswer + implements Answer { + @Override + public DescribeScalingActivitiesResponse answer(InvocationOnMock invocation) throws Throwable { + DescribeScalingActivitiesResponse response = new DescribeScalingActivitiesResponse(); + List scalingActivities = new ArrayList<>(); + ScalingActivity scalingActivity = new ScalingActivity(); + scalingActivity.setStatusCode("test-statu"); + scalingActivities.add(scalingActivity); + response.setScalingActivities(scalingActivities); + return response; + } + } +}