resolve conflict with main

This commit is contained in:
Manoj Kumar 2026-03-13 22:34:46 +05:30
commit 6bcfbc6dfc
No known key found for this signature in database
GPG Key ID: E952B7234D2C6F88
147 changed files with 7020 additions and 1010 deletions

View File

@ -301,8 +301,9 @@ public class EventTypes {
public static final String EVENT_REGISTER_CNI_CONFIG = "REGISTER.CNI.CONFIG";
public static final String EVENT_DELETE_CNI_CONFIG = "DELETE.CNI.CONFIG";
//register for user API and secret keys
//user API and secret keys
public static final String EVENT_REGISTER_FOR_SECRET_API_KEY = "REGISTER.USER.KEY";
public static final String EVENT_DELETE_SECRET_API_KEY = "DELETE.USER.KEY";
public static final String API_KEY_ACCESS_UPDATE = "API.KEY.ACCESS.UPDATE";
// Template Events

View File

@ -98,7 +98,7 @@ public interface KubernetesCluster extends ControlledEntity, com.cloud.utils.fsm
s_fsm.addTransition(State.Running, Event.ScaleDownRequested, State.Scaling);
s_fsm.addTransition(State.Stopped, Event.ScaleUpRequested, State.ScalingStoppedCluster);
s_fsm.addTransition(State.Scaling, Event.OperationSucceeded, State.Running);
s_fsm.addTransition(State.Scaling, Event.OperationFailed, State.Alert);
s_fsm.addTransition(State.Scaling, Event.OperationFailed, State.Running);
s_fsm.addTransition(State.ScalingStoppedCluster, Event.OperationSucceeded, State.Stopped);
s_fsm.addTransition(State.ScalingStoppedCluster, Event.OperationFailed, State.Alert);

View File

@ -18,6 +18,7 @@ package com.cloud.kubernetes.cluster;
import org.apache.cloudstack.acl.ControlledEntity;
import java.util.List;
import java.util.Map;
import com.cloud.user.Account;
@ -33,8 +34,10 @@ public interface KubernetesServiceHelper extends Adapter {
ControlledEntity findByUuid(String uuid);
ControlledEntity findByVmId(long vmId);
void checkVmCanBeDestroyed(UserVm userVm);
void checkVmAffinityGroupsCanBeUpdated(UserVm userVm);
boolean isValidNodeType(String nodeType);
Map<String, Long> getServiceOfferingNodeTypeMap(Map<String, Map<String, String>> serviceOfferingNodeTypeMap);
Map<String, Long> getTemplateNodeTypeMap(Map<String, Map<String, String>> templateNodeTypeMap);
Map<String, List<Long>> getAffinityGroupNodeTypeMap(Map<String, Map<String, String>> affinityGroupNodeTypeMap);
void cleanupForAccount(Account account);
}

View File

@ -19,16 +19,15 @@ package com.cloud.user;
import java.util.List;
import java.util.Map;
import com.cloud.utils.Pair;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.RolePermissionEntity;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.command.admin.account.CreateAccountCmd;
import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.RegisterUserKeyCmd;
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
import org.apache.cloudstack.auth.UserTwoFactorAuthenticator;
import org.apache.cloudstack.backup.BackupOffering;
import org.apache.cloudstack.dns.DnsServer;
import com.cloud.dc.DataCenter;
import com.cloud.domain.Domain;
@ -37,7 +36,17 @@ import com.cloud.network.vpc.VpcOffering;
import com.cloud.offering.DiskOffering;
import com.cloud.offering.NetworkOffering;
import com.cloud.offering.ServiceOffering;
import com.cloud.utils.Pair;
import org.apache.cloudstack.api.command.admin.user.DeleteUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.ListUserKeyRulesCmd;
import org.apache.cloudstack.api.command.admin.user.ListUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.RegisterUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
import org.apache.cloudstack.api.response.ApiKeyPairResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.auth.UserTwoFactorAuthenticator;
import org.apache.cloudstack.backup.BackupOffering;
import org.apache.cloudstack.dns.DnsServer;
public interface AccountService {
@ -98,7 +107,7 @@ public interface AccountService {
void markUserRegistered(long userId);
public String[] createApiKeyAndSecretKey(RegisterUserKeyCmd cmd);
ApiKeyPair createApiKeyAndSecretKey(RegisterUserKeysCmd cmd);
public String[] createApiKeyAndSecretKey(final long userId);
@ -128,6 +137,8 @@ public interface AccountService {
void validateAccountHasAccessToResource(Account account, AccessType accessType, Object resource);
void validateCallingUserHasAccessToDesiredUser(Long userId);
Long finalizeAccountId(String accountName, Long domainId, Long projectId, boolean enabledOnly);
/**
@ -137,9 +148,15 @@ public interface AccountService {
*/
UserAccount getUserAccountById(Long userId);
public Pair<Boolean, Map<String, String>> getKeys(GetUserKeysCmd cmd);
Pair<Boolean, Map<String, String>> getKeys(GetUserKeysCmd cmd);
public Pair<Boolean, Map<String, String>> getKeys(Long userId);
ListResponse<ApiKeyPairResponse> listKeys(ListUserKeysCmd cmd);
List<ApiKeyPairPermission> listKeyRules(ListUserKeyRulesCmd cmd);
void deleteApiKey(DeleteUserKeysCmd cmd);
void deleteApiKey(ApiKeyPair id);
/**
* Lists user two-factor authentication provider plugins
@ -154,4 +171,13 @@ public interface AccountService {
*/
UserTwoFactorAuthenticator getUserTwoFactorAuthenticationProvider(final Long domainId);
ApiKeyPair getLatestUserKeyPair(Long userId);
ApiKeyPair getKeyPairById(Long id);
ApiKeyPair getKeyPairByApiKey(String apiKey);
String getAccessingApiKey(BaseCmd cmd);
List<RolePermissionEntity> getAllKeypairPermissions(String apiKey);
}

View File

@ -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.
package com.cloud.user;
public enum ApiKeyPairState {
ENABLED, REMOVED, EXPIRED
}

View File

@ -65,14 +65,6 @@ public interface User extends OwnedBy, InternalIdentity {
public void setState(Account.State state);
public String getApiKey();
public void setApiKey(String apiKey);
public String getSecretKey();
public void setSecretKey(String secretKey);
public String getTimezone();
public void setTimezone(String timezone);

View File

@ -39,10 +39,6 @@ public interface UserAccount extends InternalIdentity {
String getState();
String getApiKey();
String getSecretKey();
Date getCreated();
Date getRemoved();

View File

@ -96,6 +96,7 @@ public interface VmDetailConstants {
String CKS_NODE_TYPE = "node";
String OFFERING = "offering";
String TEMPLATE = "template";
String AFFINITY_GROUP = "affinitygroup";
// VMware to KVM VM migrations specific
String VMWARE_TO_KVM_PREFIX = "vmware-to-kvm";

View File

@ -20,6 +20,7 @@ import com.cloud.exception.PermissionDeniedException;
import com.cloud.user.Account;
import com.cloud.user.User;
import com.cloud.utils.component.Adapter;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import java.util.List;
@ -31,8 +32,8 @@ public interface APIChecker extends Adapter {
// If true, apiChecker has checked the operation
// If false, apiChecker is unable to handle the operation or not implemented
// On exception, checkAccess failed don't allow
boolean checkAccess(User user, String apiCommandName) throws PermissionDeniedException;
boolean checkAccess(Account account, String apiCommandName) throws PermissionDeniedException;
boolean checkAccess(User user, String apiCommandName, ApiKeyPairPermission... apiKeyPairPermissions) throws PermissionDeniedException;
boolean checkAccess(Account account, String apiCommandName, ApiKeyPairPermission... apiKeyPairPermissions) throws PermissionDeniedException;
/**
* Verifies if the account has permission for the given list of APIs and returns only the allowed ones.
*
@ -43,4 +44,5 @@ public interface APIChecker extends Adapter {
*/
List<String> getApisAllowedToUser(Role role, User user, List<String> apiNames) throws PermissionDeniedException;
boolean isEnabled();
List<RolePermissionEntity> getImplicitRolePermissions(RoleType roleType);
}

View File

@ -21,7 +21,7 @@ import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
public interface RolePermissionEntity extends InternalIdentity, Identity {
public enum Permission {
enum Permission {
ALLOW, DENY
}
Rule getRule();

View File

@ -104,5 +104,26 @@ public interface RoleService {
List<RolePermission> findAllPermissionsBy(Long roleId);
List<RolePermissionEntity> findAllRolePermissionsEntityBy(Long roleId, boolean considerImplicitRules);
Permission getRolePermission(String permission);
int removeRolesIfNeeded(List<? extends Role> roles);
/**
* Checks if the role of the caller account has compatible permissions of the specified role permissions.
* For each permission of the {@param rolePermissionsToAccess}, the role of the caller needs to contain the same permission.
*
* @param rolePermissions the permissions of the caller role.
* @param rolePermissionsToAccess the permissions for the role that the caller role wants to access.
* @return True if the role can be accessed with the given permissions; false otherwise.
*/
boolean roleHasPermission(Map<String, RolePermissionEntity> rolePermissions, List<RolePermissionEntity> rolePermissionsToAccess);
/**
* Given a list of role permissions, returns a {@link Map} containing the API name as the key and the {@link RolePermissionEntity} for the API as the value.
*
* @param rolePermissions Permissions for the role from role.
*/
Map<String, RolePermissionEntity> getRoleRulesAndPermissions(List<RolePermissionEntity> rolePermissions);
}

View File

@ -25,16 +25,18 @@ import org.apache.commons.lang3.StringUtils;
public final class Rule {
private final String rule;
private final Pattern matchingPattern;
private final static Pattern ALLOWED_PATTERN = Pattern.compile("^[a-zA-Z0-9*]+$");
public Rule(final String rule) {
validate(rule);
this.rule = rule;
matchingPattern = Pattern.compile(rule.toLowerCase().replace("*", "(\\w*\\*?)+"));
}
public boolean matches(final String commandName) {
return StringUtils.isNotEmpty(commandName)
&& commandName.toLowerCase().matches(rule.toLowerCase().replace("*", "\\w*"));
return StringUtils.isNotEmpty(commandName) &&
matchingPattern.matcher(commandName.toLowerCase()).matches();
}
public String getRuleString() {

View File

@ -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.acl.apikeypair;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
import java.util.Date;
public interface ApiKeyPair extends ControlledEntity, InternalIdentity, Identity {
Long getUserId();
Date getStartDate();
Date getEndDate();
Date getCreated();
String getDescription();
String getApiKey();
String getSecretKey();
String getName();
Date getRemoved();
void setRemoved(Date date);
void validateDate();
boolean hasEndDatePassed();
}

View File

@ -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 org.apache.cloudstack.acl.apikeypair;
import org.apache.cloudstack.acl.RolePermissionEntity;
public interface ApiKeyPairPermission extends RolePermissionEntity {
long getApiKeyPairId();
}

View File

@ -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.acl.apikeypair;
import java.util.List;
public interface ApiKeyPairService {
List<ApiKeyPairPermission> findAllPermissionsByKeyPairId(Long apiKeyPairId, Long roleId);
ApiKeyPair findByApiKey(String apiKey);
ApiKeyPair findById(Long id);
}

View File

@ -66,5 +66,4 @@ public interface AffinityGroupService {
boolean isAffinityGroupAvailableInDomain(long affinityGroupId, long domainId);
}

View File

@ -29,6 +29,9 @@ import java.util.List;
public class AffinityProcessorBase extends AdapterBase implements AffinityGroupProcessor {
public static final String AFFINITY_TYPE_HOST = "host affinity";
public static final String AFFINITY_TYPE_HOST_ANTI = "host anti-affinity";
protected String _type;
@Override

View File

@ -19,6 +19,7 @@ package org.apache.cloudstack.api;
public class ApiConstants {
public static final String ACCOUNT = "account";
public static final String ACCOUNTS = "accounts";
public static final String ACCOUNT_NAME = "accountname";
public static final String ACCOUNT_TYPE = "accounttype";
public static final String ACCOUNT_ID = "accountid";
public static final String ACCOUNT_IDS = "accountids";
@ -46,6 +47,7 @@ public class ApiConstants {
public static final String AS_NUMBER_ID = "asnumberid";
public static final String ASN_RANGE = "asnrange";
public static final String ASN_RANGE_ID = "asnrangeid";
public static final String API_KEY_FILTER = "apikeyfilter";
public static final String ASYNC_BACKUP = "asyncbackup";
public static final String AUTO_SELECT = "autoselect";
public static final String USER_API_KEY = "userapikey";
@ -356,6 +358,7 @@ public class ApiConstants {
public static final String JOB_STATUS = "jobstatus";
public static final String KEEPALIVE_ENABLED = "keepaliveenabled";
public static final String KERNEL_VERSION = "kernelversion";
public static final String KEYPAIR_ID = "keypairid";
public static final String KEY = "key";
public static final String LABEL = "label";
public static final String LASTNAME = "lastname";
@ -521,9 +524,9 @@ public class ApiConstants {
public static final String SCHEDULE = "schedule";
public static final String SCHEDULE_ID = "scheduleid";
public static final String SCOPE = "scope";
public static final String USER_SECRET_KEY = "usersecretkey";
public static final String SEARCH_BASE = "searchbase";
public static final String SECONDARY_IP = "secondaryip";
public static final String SECRET_KEY = "secretkey";
public static final String SECURITY_GROUP_IDS = "securitygroupids";
public static final String SECURITY_GROUP_NAMES = "securitygroupnames";
public static final String SECURITY_GROUP_NAME = "securitygroupname";
@ -540,6 +543,7 @@ public class ApiConstants {
public static final String SHOW_RESOURCE_ICON = "showicon";
public static final String SHOW_INACTIVE = "showinactive";
public static final String SHOW_UNIQUE = "showunique";
public static final String SHOW_PERMISSIONS = "showpermissions";
public static final String SIGNATURE = "signature";
public static final String SIGNATURE_VERSION = "signatureversion";
public static final String SINCE = "since";
@ -623,7 +627,6 @@ public class ApiConstants {
public static final String USERNAME = "username";
public static final String USER_CONFIGURABLE = "userconfigurable";
public static final String USER_SECURITY_GROUP_LIST = "usersecuritygrouplist";
public static final String USER_SECRET_KEY = "usersecretkey";
public static final String USE_VIRTUAL_NETWORK = "usevirtualnetwork";
public static final String USE_VIRTUAL_ROUTER_IP_RESOLVER = "userouteripresolver";
public static final String UPDATE_IN_SEQUENCE = "updateinsequence";
@ -765,6 +768,7 @@ public class ApiConstants {
public static final String ROLE_TYPE = "roletype";
public static final String ROLE_NAME = "rolename";
public static final String PERMISSION = "permission";
public static final String PERMISSIONS = "permissions";
public static final String RULE = "rule";
public static final String RULES = "rules";
public static final String RULE_ID = "ruleid";
@ -1028,7 +1032,7 @@ public class ApiConstants {
public static final String NSX_PROVIDER_PORT = "nsxproviderport";
public static final String NSX_CONTROLLER_ID = "nsxcontrollerid";
public static final String S3_ACCESS_KEY = "accesskey";
public static final String S3_SECRET_KEY = "secretkey";
public static final String SECRET_KEY = "secretkey";
public static final String S3_END_POINT = "endpoint";
public static final String S3_BUCKET_NAME = "bucket";
public static final String S3_SIGNER = "s3signer";
@ -1240,6 +1244,13 @@ public class ApiConstants {
public static final String MAX_SIZE = "maxsize";
public static final String NODE_TYPE_OFFERING_MAP = "nodeofferings";
public static final String NODE_TYPE_TEMPLATE_MAP = "nodetemplates";
public static final String NODE_TYPE_AFFINITY_GROUP_MAP = "nodeaffinitygroups";
public static final String CONTROL_AFFINITY_GROUP_IDS = "controlaffinitygroupids";
public static final String CONTROL_AFFINITY_GROUP_NAMES = "controlaffinitygroupnames";
public static final String WORKER_AFFINITY_GROUP_IDS = "workeraffinitygroupids";
public static final String WORKER_AFFINITY_GROUP_NAMES = "workeraffinitygroupnames";
public static final String ETCD_AFFINITY_GROUP_IDS = "etcdaffinitygroupids";
public static final String ETCD_AFFINITY_GROUP_NAMES = "etcdaffinitygroupnames";
public static final String BOOT_TYPE = "boottype";
public static final String BOOT_MODE = "bootmode";

View File

@ -49,5 +49,7 @@ public interface ApiServerService {
boolean resetPassword(UserAccount userAccount, String token, String password);
String getDomainId(Map<String, Object[]> params);
boolean isPostRequestsAndTimestampsEnforced();
}

View File

@ -29,6 +29,7 @@ public abstract class BaseAsyncCmd extends BaseCmd {
public static final String migrationSyncObject = "migration";
public static final String snapshotHostSyncObject = "snapshothost";
public static final String gslbSyncObject = "globalserverloadbalancer";
public static final String user = "user";
private Object job;

View File

@ -36,6 +36,7 @@ import com.cloud.bgp.BGPService;
import org.apache.cloudstack.acl.ProjectRoleService;
import org.apache.cloudstack.acl.RoleService;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairService;
import org.apache.cloudstack.affinity.AffinityGroupService;
import org.apache.cloudstack.alert.AlertService;
import org.apache.cloudstack.annotation.AnnotationService;
@ -222,6 +223,8 @@ public abstract class BaseCmd {
@Inject
public Ipv6Service ipv6Service;
@Inject
public ApiKeyPairService apiKeyPairService;
@Inject
public VnfTemplateManager vnfTemplateManager;
@Inject
public BucketApiService _bucketService;

View File

@ -24,6 +24,8 @@ import java.util.Set;
import org.apache.cloudstack.api.response.ConsoleSessionResponse;
import org.apache.cloudstack.consoleproxy.ConsoleSession;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import org.apache.cloudstack.affinity.AffinityGroup;
import org.apache.cloudstack.affinity.AffinityGroupResponse;
import org.apache.cloudstack.api.ApiConstants.HostDetails;
@ -41,6 +43,7 @@ import org.apache.cloudstack.api.response.AutoScaleVmProfileResponse;
import org.apache.cloudstack.api.response.BackupOfferingResponse;
import org.apache.cloudstack.api.response.BackupRepositoryResponse;
import org.apache.cloudstack.api.response.BackupScheduleResponse;
import org.apache.cloudstack.api.response.BaseRolePermissionResponse;
import org.apache.cloudstack.api.response.BucketResponse;
import org.apache.cloudstack.api.response.CapacityResponse;
import org.apache.cloudstack.api.response.ClusterResponse;
@ -77,6 +80,7 @@ import org.apache.cloudstack.api.response.InternalLoadBalancerElementResponse;
import org.apache.cloudstack.api.response.IpForwardingRuleResponse;
import org.apache.cloudstack.api.response.IpQuarantineResponse;
import org.apache.cloudstack.api.response.IsolationMethodResponse;
import org.apache.cloudstack.api.response.ApiKeyPairResponse;
import org.apache.cloudstack.api.response.LBHealthCheckResponse;
import org.apache.cloudstack.api.response.LBStickinessResponse;
import org.apache.cloudstack.api.response.ListResponse;
@ -583,4 +587,8 @@ public interface ResponseGenerator {
GuiThemeResponse createGuiThemeResponse(GuiThemeJoin guiThemeJoin);
ConsoleSessionResponse createConsoleSessionResponse(ConsoleSession consoleSession, ResponseView responseView);
ApiKeyPairResponse createKeyPairResponse(ApiKeyPair keyPair);
ListResponse<BaseRolePermissionResponse> createKeypairPermissionsResponse(List<ApiKeyPairPermission> permissions);
}

View File

@ -75,7 +75,7 @@ public class CreatePhysicalNetworkCmd extends BaseAsyncCreateCmd {
@Parameter(name = ApiConstants.ISOLATION_METHODS,
type = CommandType.LIST,
collectionType = CommandType.STRING,
description = "The isolation method for the physical Network[VLAN/L3/GRE]")
description = "The isolation method for the physical Network[VLAN/VXLAN/GRE/STT/BCF_SEGMENT/SSP/ODL/L3VPN/VCS/NSX/NETRIS]")
private List<String> isolationMethods;
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "The name of the physical Network")

View File

@ -27,7 +27,7 @@ import static org.apache.cloudstack.api.ApiConstants.S3_END_POINT;
import static org.apache.cloudstack.api.ApiConstants.S3_HTTPS_FLAG;
import static org.apache.cloudstack.api.ApiConstants.S3_MAX_ERROR_RETRY;
import static org.apache.cloudstack.api.ApiConstants.S3_SIGNER;
import static org.apache.cloudstack.api.ApiConstants.S3_SECRET_KEY;
import static org.apache.cloudstack.api.ApiConstants.SECRET_KEY;
import static org.apache.cloudstack.api.ApiConstants.S3_SOCKET_TIMEOUT;
import static org.apache.cloudstack.api.ApiConstants.S3_USE_TCP_KEEPALIVE;
import static org.apache.cloudstack.api.BaseCmd.CommandType.BOOLEAN;
@ -64,7 +64,7 @@ public final class AddImageStoreS3CMD extends BaseCmd implements ClientOptions {
@Parameter(name = S3_ACCESS_KEY, type = STRING, required = true, description = "S3 access key")
private String accessKey;
@Parameter(name = S3_SECRET_KEY, type = STRING, required = true, description = "S3 secret key")
@Parameter(name = SECRET_KEY, type = STRING, required = true, description = "S3 secret key")
private String secretKey;
@Parameter(name = S3_END_POINT, type = STRING, required = true, description = "S3 endpoint")
@ -101,7 +101,7 @@ public final class AddImageStoreS3CMD extends BaseCmd implements ClientOptions {
Map<String, String> dm = new HashMap();
dm.put(ApiConstants.S3_ACCESS_KEY, getAccessKey());
dm.put(ApiConstants.S3_SECRET_KEY, getSecretKey());
dm.put(ApiConstants.SECRET_KEY, getSecretKey());
dm.put(ApiConstants.S3_END_POINT, getEndPoint());
dm.put(ApiConstants.S3_BUCKET_NAME, getBucketName());

View File

@ -0,0 +1,81 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES 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.user;
import com.cloud.event.EventTypes;
import com.cloud.user.Account;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.api.ACL;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseAsyncCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.ApiKeyPairResponse;
import org.apache.cloudstack.api.response.SuccessResponse;
@APICommand(name = "deleteUserKeys", description = "Deletes a keypair from a user", responseObject = SuccessResponse.class,
since = "4.23.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
public class DeleteUserKeysCmd extends BaseAsyncCmd {
@ACL
@Parameter(name = ApiConstants.KEYPAIR_ID, type = CommandType.UUID, entityType = ApiKeyPairResponse.class, required = true, description = "ID of the keypair to be deleted.")
private Long id;
@Override
public ApiCommandResourceType getApiResourceType() {
return ApiCommandResourceType.User;
}
@Override
public long getEntityOwnerId() {
ApiKeyPair keyPair = apiKeyPairService.findById(id);
if (keyPair != null) {
return keyPair.getAccountId();
}
return Account.ACCOUNT_ID_SYSTEM;
}
public Long getId() {
return id;
}
@Override
public void execute() {
_accountService.deleteApiKey(this);
SuccessResponse response = new SuccessResponse(getCommandName());
this.setResponseObject(response);
}
@Override
public String getEventType() {
return EventTypes.EVENT_DELETE_SECRET_API_KEY;
}
@Override
public String getEventDescription() {
ApiKeyPair keyPair = apiKeyPairService.findById(id);
return String.format("Deleting API key pair with ID [%s]%s",
keyPair == null ? id : keyPair.getUuid(),
keyPair == null ? "." : String.format(" and name [%s].", keyPair.getName()));
}
@Override
public Long getSyncObjId() {
return getId();
}
}

View File

@ -32,33 +32,33 @@ import org.apache.cloudstack.api.response.UserResponse;
import java.util.Map;
@APICommand(name = "getUserKeys",
description = "This command allows the user to query the seceret and API keys for the account",
responseObject = RegisterUserKeyResponse.class,
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = true,
authorized = {RoleType.User, RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin},
since = "4.10.0")
public class GetUserKeysCmd extends BaseCmd{
@Parameter(name= ApiConstants.ID, type = CommandType.UUID, entityType = UserResponse.class, required = true, description = "ID of the user whose keys are required")
description = "Queries the last registered secret and API keys of a user.",
responseObject = RegisterUserKeyResponse.class,
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = true,
authorized = {RoleType.User, RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin},
since = "4.10.0")
public class GetUserKeysCmd extends BaseCmd {
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = UserResponse.class, required = true, description = "ID of the user whose keys are required")
private Long id;
public Long getID(){
public Long getId() {
return id;
}public long getEntityOwnerId(){
User user = _entityMgr.findById(User.class, getID());
if(user != null){
}
public long getEntityOwnerId() {
User user = _entityMgr.findById(User.class, getId());
if (user != null) {
return user.getAccountId();
}
else return Account.ACCOUNT_ID_SYSTEM;
return Account.ACCOUNT_ID_SYSTEM;
}
public void execute(){
public void execute() {
Pair<Boolean, Map<String, String>> keys = _accountService.getKeys(this);
RegisterUserKeyResponse response = new RegisterUserKeyResponse();
if(keys != null){
if (keys != null){
response.setApiKeyAccess(keys.first());
response.setApiKey(keys.second().get("apikey"));
response.setSecretKey(keys.second().get("secretkey"));

View File

@ -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 org.apache.cloudstack.api.command.admin.user;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.user.Account;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import org.apache.cloudstack.api.ACL;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.ApiKeyPairResponse;
import org.apache.cloudstack.api.response.BaseRolePermissionResponse;
import org.apache.cloudstack.api.response.ListResponse;
import java.util.List;
@APICommand(name = "listUserKeyRules",
description = "Lists the rules defined for a API key pair.",
responseObject = BaseRolePermissionResponse.class,
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = false,
since = "4.23.0")
public class ListUserKeyRulesCmd extends BaseCmd {
@ACL
@Parameter(name = ApiConstants.KEYPAIR_ID, type = CommandType.UUID, entityType = ApiKeyPairResponse.class, description = "ID of the key pair.", required = true)
private Long id;
public Long getId() {
return id;
}
public long getEntityOwnerId() {
ApiKeyPair keyPair = apiKeyPairService.findById(getId());
if (keyPair != null) {
return keyPair.getAccountId();
}
return Account.ACCOUNT_ID_SYSTEM;
}
@Override
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException {
List<ApiKeyPairPermission> permissions = _accountService.listKeyRules(this);
ListResponse<BaseRolePermissionResponse> response = _responseGenerator.createKeypairPermissionsResponse(permissions);
response.setResponseName(getCommandName());
setResponseObject(response);
}
}

View File

@ -0,0 +1,101 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES 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.user;
import com.cloud.user.Account;
import com.cloud.user.UserAccount;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.api.ACL;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseListCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.ApiKeyPairResponse;
import org.apache.cloudstack.api.response.UserResponse;
@APICommand(name = "listUserKeys",
description = "Lists the API key pairs (API and secret keys) of a user.",
responseObject = ApiKeyPairResponse.class,
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = true,
authorized = {RoleType.User, RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin},
since = "4.23.0")
public class ListUserKeysCmd extends BaseListCmd {
@ACL
@Parameter(name = ApiConstants.USER_ID, type = CommandType.UUID, entityType = UserResponse.class, description = "ID of the user that owns the keys.")
private Long userId;
@ACL
@Parameter(name = ApiConstants.KEYPAIR_ID, type = CommandType.UUID, entityType = ApiKeyPairResponse.class, description = "ID of the key pair.")
private Long keyPairId;
@Parameter(name = ApiConstants.API_KEY_FILTER, type = CommandType.STRING, description = "API key of the key pair.")
private String apiKeyFilter;
@Parameter(name = ApiConstants.SHOW_PERMISSIONS, type = CommandType.BOOLEAN, description = "Whether API Key rules should be returned. Defaults to false.")
private boolean showPermissions = false;
@Parameter(name = ApiConstants.LIST_ALL, type = CommandType.BOOLEAN, authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin},
description = "Lists all API key pairs of users that are accessible by the calling account. Only available for administrators. Defaults to false.")
private boolean listAll = false;
public Long getUserId() {
return userId;
}
public Long getKeyId() {
return keyPairId;
}
public String getApiKeyFilter() {
return apiKeyFilter;
}
public Boolean getShowPermissions() {
return showPermissions;
}
public boolean getListAll() {
return listAll;
}
public long getEntityOwnerId() {
if (getKeyId() != null) {
ApiKeyPair keypair = apiKeyPairService.findById(getKeyId());
if (keypair != null) {
return keypair.getAccountId();
}
} else if (getUserId() != null) {
UserAccount userAccount = _accountService.getUserAccountById(getUserId());
if (userAccount != null) {
return userAccount.getAccountId();
}
}
return Account.ACCOUNT_ID_SYSTEM;
}
public void execute() {
ListResponse<ApiKeyPairResponse> finalResponse = _accountService.listKeys(this);
finalResponse.setObjectName("userkeys");
finalResponse.setResponseName(getCommandName());
setResponseObject(finalResponse);
}
}

View File

@ -1,93 +0,0 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES 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.user;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.RegisterUserKeyResponse;
import org.apache.cloudstack.api.response.UserResponse;
import com.cloud.user.Account;
import com.cloud.user.User;
@APICommand(name = "registerUserKeys",
responseObject = RegisterUserKeyResponse.class,
description = "This command allows a user to register for the developer API, returning a secret key and an API key. This request is made through the integration API port, so it is a privileged command and must be made on behalf of a user. It is up to the implementer just how the username and password are entered, and then how that translates to an integration API request. Both secret key and API key should be returned to the user",
requestHasSensitiveInfo = false, responseHasSensitiveInfo = true)
public class RegisterUserKeyCmd extends BaseCmd {
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = UserResponse.class, required = true, description = "User id")
private Long id;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public long getEntityOwnerId() {
User user = _entityMgr.findById(User.class, getId());
if (user != null) {
return user.getAccountId();
}
return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked
}
@Override
public Long getApiResourceId() {
return id;
}
@Override
public ApiCommandResourceType getApiResourceType() {
return ApiCommandResourceType.User;
}
@Override
public void execute() {
String[] keys = _accountService.createApiKeyAndSecretKey(this);
RegisterUserKeyResponse response = new RegisterUserKeyResponse();
if (keys != null) {
response.setApiKey(keys[0]);
response.setSecretKey(keys[1]);
}
response.setObjectName("userkeys");
response.setResponseName(getCommandName());
this.setResponseObject(response);
}
}

View File

@ -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 org.apache.cloudstack.api.command.admin.user;
import com.cloud.event.EventTypes;
import com.cloud.user.Account;
import com.cloud.user.User;
import org.apache.cloudstack.acl.Rule;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseAsyncCmd;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.commons.lang3.StringUtils;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.ApiKeyPairResponse;
import org.apache.cloudstack.api.response.UserResponse;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@APICommand(name = "registerUserKeys",
responseObject = ApiKeyPairResponse.class,
description = "Registers an API key pair (API and secret keys) for a user.",
requestHasSensitiveInfo = false, responseHasSensitiveInfo = true)
public class RegisterUserKeysCmd extends BaseAsyncCmd {
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = UserResponse.class, required = true, description = "ID of the user.")
private Long id;
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "API key pair name.")
private String name;
@Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "API key pair description.")
private String description;
@Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, description = "Start date of the API key pair. " +
ApiConstants.PARAMETER_DESCRIPTION_START_DATE_POSSIBLE_FORMATS)
private Date startDate;
@Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, description = "Expiration date of the API key pair. " +
ApiConstants.PARAMETER_DESCRIPTION_END_DATE_POSSIBLE_FORMATS)
private Date endDate;
@Parameter(name = ApiConstants.RULES, type = CommandType.MAP, description = "The rules of the API key pair. If no rules are informed, " +
"defaults to allowing all account permissions. Otherwise, only the explicitly informed permissions for the key pair will be " +
"considered. Lower indexed rules take precedence over higher. Thus, in the following example: " +
"\"rules[0].rule=deleteUserKeys rules[0].permission=deny rules[1].rule=*UserKey* rules[1].permission=allow\", all rules matching " +
"the expression \"*UserKeys*\" will be allowed, except for \"deleteUserKeys\".")
private Map rules;
public void setUserId(Long userId) {
this.id = userId;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Date getStartDate() {
return startDate;
}
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
public Date getEndDate() {
return endDate;
}
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
public void setRules(Map rules) {
this.rules = rules;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
public List<Map<String, Object>> getRules() {
List<Map<String, Object>> rulesDetails = new ArrayList<>();
if (rules == null) {
return rulesDetails;
}
for (Object ruleObject : rules.values()) {
HashMap<String, String> detail = (HashMap<String, String>) ruleObject;
Map<String, Object> ruleDetails = new HashMap<>();
String rule = detail.get(ApiConstants.RULE);
if (StringUtils.isEmpty(rule)) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Empty rule has been provided in the [rules] parameter.");
}
String permission = detail.get(ApiConstants.PERMISSION);
if (StringUtils.isEmpty(permission)) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, String.format("Rule [%s] has no permission associated with it," +
" please specify if it is either [allow] or [deny].", rule));
}
ruleDetails.put(ApiConstants.RULE, new Rule(rule));
ruleDetails.put(ApiConstants.PERMISSION, roleService.getRolePermission(permission));
String description = detail.get(ApiConstants.DESCRIPTION);
if (StringUtils.isNotEmpty(description)) {
ruleDetails.put(ApiConstants.DESCRIPTION, description);
}
rulesDetails.add(ruleDetails);
}
return rulesDetails;
}
@Override
public long getEntityOwnerId() {
User user = _entityMgr.findById(User.class, getUserId());
List<Long> accessibleUsers = _queryService.searchForAccessibleUsers();
if (user != null && accessibleUsers.stream().anyMatch(u -> u == user.getId())) {
return user.getAccountId();
}
return Account.ACCOUNT_ID_SYSTEM;
}
public Long getUserId() {
return id;
}
@Override
public Long getApiResourceId() {
User user = _entityMgr.findById(User.class, getUserId());
if (user != null) {
return user.getId();
}
return null;
}
@Override
public ApiCommandResourceType getApiResourceType() {
return ApiCommandResourceType.User;
}
@Override
public void execute() {
ApiKeyPair apiKeyPair = _accountService.createApiKeyAndSecretKey(this);
ApiKeyPairResponse response = new ApiKeyPairResponse();
if (apiKeyPair != null) {
response = _responseGenerator.createKeyPairResponse(apiKeyPair);
}
response.setObjectName("userkeys");
response.setResponseName(getCommandName());
this.setResponseObject(response);
}
@Override
public String getEventType() {
return EventTypes.EVENT_REGISTER_FOR_SECRET_API_KEY;
}
@Override
public String getEventDescription() {
String userUuid = getResourceUuid(ApiConstants.ID);
return String.format("Registering API keypair for user [%s].", userUuid == null ? id : userUuid);
}
@Override
public String getSyncObjType() {
return BaseAsyncCmd.user;
}
@Override
public Long getSyncObjId() {
return getUserId();
}
}

View File

@ -46,8 +46,8 @@ public class UpdateUserCmd extends BaseCmd {
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.USER_API_KEY, type = CommandType.STRING, description = "The API key for the user. Must be specified with userSecretKey")
private String apiKey;
@Parameter(name = ApiConstants.USER_API_KEY, type = CommandType.STRING, description = "Updates the latest API key of the user. Must be specified with usersecretkey")
private String userApiKey;
@Parameter(name = ApiConstants.EMAIL, type = CommandType.STRING, description = "Email")
private String email;
@ -70,8 +70,8 @@ public class UpdateUserCmd extends BaseCmd {
@Parameter(name = ApiConstants.CURRENT_PASSWORD, type = CommandType.STRING, description = "Current password that was being used by the user. You must inform the current password when updating the password.", acceptedOnAdminPort = false)
private String currentPassword;
@Parameter(name = ApiConstants.USER_SECRET_KEY, type = CommandType.STRING, description = "The secret key for the user. Must be specified with userApiKey")
private String secretKey;
@Parameter(name = ApiConstants.USER_SECRET_KEY, type = CommandType.STRING, description = "Updates the latest secret key of the user. Must be specified with userapikey.")
private String userSecretKey;
@Parameter(name = ApiConstants.API_KEY_ACCESS, type = CommandType.STRING, description = "Determines if Api key access for this user is enabled, disabled or inherits the value from its parent, the owning account", since = "4.20.1.0", authorized = {RoleType.Admin})
private String apiKeyAccess;
@ -99,7 +99,7 @@ public class UpdateUserCmd extends BaseCmd {
/////////////////////////////////////////////////////
public String getApiKey() {
return apiKey;
return userApiKey;
}
public String getEmail() {
@ -127,7 +127,7 @@ public class UpdateUserCmd extends BaseCmd {
}
public String getSecretKey() {
return secretKey;
return userSecretKey;
}
public String getApiKeyAccess() {

View File

@ -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 org.apache.cloudstack.api.response;
import com.cloud.user.ApiKeyPairState;
import com.google.gson.annotations.SerializedName;
import java.util.Date;
import java.util.List;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.api.ApiConstants;
import com.cloud.serializer.Param;
import org.apache.cloudstack.api.BaseResponseWithAnnotations;
import org.apache.cloudstack.api.EntityReference;
@EntityReference(value = ApiKeyPair.class)
public class ApiKeyPairResponse extends BaseResponseWithAnnotations {
@SerializedName(ApiConstants.NAME)
@Param(description = "Name of the API key pair")
private String name;
@SerializedName(ApiConstants.API_KEY)
@Param(description = "The API key of the registered user.", isSensitive = true)
private String userApiKey;
@SerializedName(ApiConstants.SECRET_KEY)
@Param(description = "The secret key of the registered user.", isSensitive = true)
private String userSecretKey;
@SerializedName(ApiConstants.USER_ID)
@Param(description = "ID of the user that owns the keypair.")
private String userId;
@SerializedName(ApiConstants.USERNAME)
@Param(description = "Username of the keypair's owner.")
private String username;
@SerializedName(ApiConstants.ID)
@Param(description = "ID of the API key pair.", isSensitive = true)
private String id;
@SerializedName(ApiConstants.DESCRIPTION)
@Param(description = "API key pair description.")
private String description;
@SerializedName(ApiConstants.START_DATE)
@Param(description = "API key pair start date.")
private Date startDate;
@SerializedName(ApiConstants.END_DATE)
@Param(description = "API key pair expiration date.")
private Date endDate;
@SerializedName(ApiConstants.CREATED)
@Param(description = "API key pair creation timestamp.")
private Date created;
@SerializedName(ApiConstants.ACCOUNT_TYPE)
@Param(description = "Account type.")
private String accountType;
@SerializedName(ApiConstants.ACCOUNT_ID)
@Param(description = "Account ID.")
private String accountId;
@SerializedName(ApiConstants.ACCOUNT_NAME)
@Param(description = "Account name.")
private String accountName;
@SerializedName(ApiConstants.ROLE_ID)
@Param(description = "ID of the role.")
private String roleId;
@SerializedName(ApiConstants.ROLE_TYPE)
@Param(description = "Type of the role (Admin, ResourceAdmin, DomainAdmin, User).")
private String roleType;
@SerializedName(ApiConstants.ROLE_NAME)
@Param(description = "Name of the role.")
private String roleName;
@SerializedName(ApiConstants.PERMISSIONS)
@Param(description = "Permissions of the API key pair.")
private List<BaseRolePermissionResponse> permissions;
@SerializedName(ApiConstants.DOMAIN_ID)
@Param(description = "ID of the domain which the account belongs to.")
private String domainId;
@SerializedName(ApiConstants.DOMAIN)
@Param(description = "Name of the domain which the account belongs to.")
private String domainName;
@SerializedName(ApiConstants.DOMAIN_PATH)
@Param(description = "Path of the domain which the account belongs to.")
private String domainPath;
@SerializedName(ApiConstants.STATE)
@Param(description = "State of the API key pair.")
private ApiKeyPairState state;
public String getApiKey() {
return userApiKey;
}
public void setApiKey(String apiKey) {
this.userApiKey = apiKey;
}
public String getSecretKey() {
return userSecretKey;
}
public void setSecretKey(String secretKey) {
this.userSecretKey = secretKey;
}
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Date getStartDate() {
return startDate;
}
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
public Date getEndDate() {
return endDate;
}
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAccountType() {
return accountType;
}
public void setAccountType(String accountType) {
this.accountType = accountType;
}
public String getRoleId() {
return roleId;
}
public void setRoleId(String roleId) {
this.roleId = roleId;
}
public String getAccountId() {
return accountId;
}
public void setAccountId(String accountId) {
this.accountId = accountId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getRoleType() {
return roleType;
}
public void setRoleType(String roleType) {
this.roleType = roleType;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public String getDomainId() {
return domainId;
}
public void setDomainId(String domainId) {
this.domainId = domainId;
}
public String getDomainName() {
return domainName;
}
public void setDomainName(String domainName) {
this.domainName = domainName;
}
public String getDomainPath() {
return domainPath;
}
public void setDomainPath(String domainPath) {
this.domainPath = domainPath;
}
public ApiKeyPairState getState() {
return state;
}
public void setState(ApiKeyPairState state) {
this.state = state;
}
public String getAccountName() {
return accountName;
}
public void setAccountName(String accountName) {
this.accountName = accountName;
}
public List<BaseRolePermissionResponse> getPermissions() {
return permissions;
}
public void setPermissions(List<BaseRolePermissionResponse> permissions) {
this.permissions = permissions;
}
}

View File

@ -95,12 +95,12 @@ public class UserResponse extends BaseResponse implements SetResourceIconRespons
@Param(description = "The timezone user was created in")
private String timezone;
@SerializedName("apikey")
@SerializedName(ApiConstants.API_KEY)
@Param(description = "The API key of the user", isSensitive = true)
private String apiKey;
@Deprecated
@SerializedName("secretkey")
@SerializedName(ApiConstants.SECRET_KEY)
@Param(description = "The secret key of the user", isSensitive = true)
private String secretKey;

View File

@ -145,6 +145,8 @@ public interface QueryService {
ListResponse<UserResponse> searchForUsers(Long domainId, boolean recursive) throws PermissionDeniedException;
List<Long> searchForAccessibleUsers();
ListResponse<EventResponse> searchForEvents(ListEventsCmd cmd);
ListResponse<ResourceTagResponse> listTags(ListTagsCmd cmd);

View File

@ -17,13 +17,46 @@
package org.apache.cloudstack.acl;
import com.cloud.exception.InvalidParameterValueException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import org.apache.cloudstack.api.APICommand;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.Arrays;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
public class RuleTest {
private static List<String> apiNames;
private static List<Rule> apiRules;
private static ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
@BeforeClass
public static void setup() {
provider.addIncludeFilter(new AnnotationTypeFilter(APICommand.class));
Set<BeanDefinition> beanDefinitions = provider.findCandidateComponents("org.apache.cloudstack.api");
apiNames = new ArrayList<>();
apiRules = new ArrayList<>();
for(BeanDefinition bd : beanDefinitions) {
if (bd instanceof AnnotatedBeanDefinition) {
Map<String, Object> annotationAttributeMap = ((AnnotatedBeanDefinition) bd).getMetadata()
.getAnnotationAttributes(APICommand.class.getName());
String apiName = annotationAttributeMap.get("name").toString();
apiNames.add(apiName);
apiRules.add(new Rule(apiName));
}
}
}
@Test
public void testToString() throws Exception {
Rule rule = new Rule("someString");
@ -31,21 +64,89 @@ public class RuleTest {
}
@Test
public void testMatchesEmpty() throws Exception {
Rule rule = new Rule("someString");
Assert.assertFalse(rule.matches(""));
public void ruleMatchesTestNoMatchesOnEmptyString() throws Exception {
String testCmd = "";
List<String> matches = new ArrayList<>();
for (Rule rule : apiRules) {
if (rule.matches(testCmd)) {
matches.add(rule.getRuleString());
}
}
Assert.assertEquals(matches.size(), 0);
}
@Test
public void testMatchesNull() throws Exception {
Rule rule = new Rule("someString");
Assert.assertFalse(rule.matches(null));
public void ruleMatchesTestNoMatchesOnNull() throws Exception {
List<String> matches = new ArrayList<>();
for (Rule rule : apiRules) {
if (rule.matches(null)) {
matches.add(rule.getRuleString());
}
}
Assert.assertTrue(matches.isEmpty());
}
@Test
public void testMatchesSpace() throws Exception {
Rule rule = new Rule("someString");
Assert.assertFalse(rule.matches(" "));
public void ruleMatchesTestNoMatchesOnSpaceCharacter() throws Exception {
String testCmd = " ";
List<String> matches = new ArrayList<>();
for (Rule rule : apiRules) {
if (rule.matches(testCmd)) {
matches.add(rule.getRuleString());
}
}
Assert.assertTrue(matches.isEmpty());
}
@Test
public void ruleMatchesTestWildCardOnEndWorksAsNormalRegex() {
setup();
Pattern regexPattern = Pattern.compile("list.*");
Rule acsRegexRule = new Rule("list*");
List<String> nonMatches = new ArrayList<>();
for (String apiName : apiNames) {
if (acsRegexRule.matches(apiName) != regexPattern.matcher(apiName).matches()) {
nonMatches.add(apiName);
}
}
Assert.assertTrue(nonMatches.isEmpty());
}
@Test
public void ruleMatchesTestWildCardOnMiddleWorksAsNormalRegex() {
setup();
Pattern regexPattern = Pattern.compile("list.*s");
Rule acsRegexRule = new Rule("list*s");
List<String> nonMatches = new ArrayList<>();
for (String apiName : apiNames) {
if (acsRegexRule.matches(apiName) != regexPattern.matcher(apiName).matches()) {
nonMatches.add(apiName);
}
}
Assert.assertTrue(nonMatches.isEmpty());
}
@Test
public void ruleMatchesTestWildCardOnStartWorksAsNormalRegex() {
setup();
Pattern regexPattern = Pattern.compile(".*User");
Rule acsRegexRule = new Rule("*User");
List<String> nonMatches = new ArrayList<>();
for (String apiName : apiNames) {
if (acsRegexRule.matches(apiName) != regexPattern.matcher(apiName).matches()) {
nonMatches.add(apiName);
}
}
Assert.assertTrue(nonMatches.isEmpty());
}
@Test
@ -73,7 +174,25 @@ public class RuleTest {
}
@Test
public void testValidateRuleWithValidData() throws Exception {
public void ruleMatchesTestWildcardOnRuleAndCommand() throws Exception {
Rule rule = new Rule("*");
Assert.assertTrue(rule.matches("list*"));
}
@Test
public void ruleMatchesTestWildcardOnRuleAndCommandNotAllowed() throws Exception {
Rule rule = new Rule("list*");
Assert.assertFalse(rule.matches("*"));
}
@Test
public void ruleMatchesTestWithMultipleStars() throws Exception {
Rule rule = new Rule("list***");
Assert.assertFalse(rule.matches("api"));
}
@Test
public void testRuleToStringWithValidStrings() throws Exception {
for (String rule : Arrays.asList("a", "1", "someApi", "someApi321", "123SomeApi",
"prefix*", "*middle*", "*Suffix",
"*", "**", "f***", "m0nk3yMa**g1c*")) {
@ -82,7 +201,7 @@ public class RuleTest {
}
@Test
public void testValidateRuleWithInvalidData() throws Exception {
public void testRuleToStringWithInvalidStrings() throws Exception {
for (String rule : Arrays.asList(null, "", " ", " ", "\n", "\t", "\r", "\"", "\'",
"^someApi$", "^someApi", "some$", "some-Api;", "some,Api",
"^", "$", "^$", ".*", "\\w+", "r**l3rd0@Kr3", "j@s1n|+|0ȷ",

View File

@ -1947,7 +1947,7 @@ public class Upgrade410to420 extends DbUpgradeAbstractImpl {
Map<String, String> detailMap = new HashMap<String, String>();
detailMap.put(ApiConstants.S3_ACCESS_KEY, s3_accesskey);
detailMap.put(ApiConstants.S3_SECRET_KEY, s3_secretkey);
detailMap.put(ApiConstants.SECRET_KEY, s3_secretkey);
detailMap.put(ApiConstants.S3_BUCKET_NAME, s3_bucket);
detailMap.put(ApiConstants.S3_END_POINT, s3_endpoint);
detailMap.put(ApiConstants.S3_HTTPS_FLAG, String.valueOf(s3_https));

View File

@ -36,7 +36,6 @@ import org.apache.cloudstack.api.InternalIdentity;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import org.apache.commons.lang3.StringUtils;
import com.cloud.utils.db.Encrypt;
import com.cloud.utils.db.GenericDao;
@Entity
@ -69,13 +68,6 @@ public class UserAccountVO implements UserAccount, InternalIdentity {
@Column(name = "state")
private String state;
@Column(name = "api_key")
private String apiKey = null;
@Encrypt
@Column(name = "secret_key")
private String secretKey = null;
@Column(name = GenericDao.CREATED_COLUMN)
private Date created;
@ -203,24 +195,6 @@ public class UserAccountVO implements UserAccount, InternalIdentity {
this.state = state;
}
@Override
public String getApiKey() {
return apiKey;
}
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
@Override
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
@Override
public Date getCreated() {
return created;

View File

@ -33,7 +33,6 @@ import org.apache.cloudstack.api.InternalIdentity;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import com.cloud.user.Account.State;
import com.cloud.utils.db.Encrypt;
import com.cloud.utils.db.GenericDao;
import org.apache.commons.lang3.StringUtils;
@ -71,13 +70,6 @@ public class UserVO implements User, Identity, InternalIdentity {
@Enumerated(value = EnumType.STRING)
private State state;
@Column(name = "api_key")
private String apiKey = null;
@Encrypt
@Column(name = "secret_key")
private String secretKey = null;
@Column(name = GenericDao.CREATED_COLUMN)
private Date created;
@ -150,8 +142,6 @@ public class UserVO implements User, Identity, InternalIdentity {
this.setTimezone(user.getTimezone());
this.setUuid(user.getUuid());
this.setSource(user.getSource());
this.setApiKey(user.getApiKey());
this.setSecretKey(user.getSecretKey());
this.setExternalEntity(user.getExternalEntity());
this.setRegistered(user.isRegistered());
this.setRegistrationToken(user.getRegistrationToken());
@ -243,26 +233,6 @@ public class UserVO implements User, Identity, InternalIdentity {
this.state = state;
}
@Override
public String getApiKey() {
return apiKey;
}
@Override
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
@Override
public String getSecretKey() {
return secretKey;
}
@Override
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
@Override
public String getTimezone() {
if (StringUtils.isEmpty(timezone)) {

View File

@ -30,6 +30,4 @@ public interface UserAccountDao extends GenericDao<UserAccountVO, Long> {
List<UserAccountVO> getUserAccountByEmail(String email, Long domainId);
boolean validateUsernameInDomain(String username, Long domainId);
UserAccount getUserByApiKey(String apiKey);
}

View File

@ -19,7 +19,6 @@ package com.cloud.user.dao;
import com.cloud.user.UserAccount;
import com.cloud.user.UserAccountVO;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import org.springframework.stereotype.Component;
@ -28,14 +27,6 @@ import java.util.List;
@Component
public class UserAccountDaoImpl extends GenericDaoBase<UserAccountVO, Long> implements UserAccountDao {
protected final SearchBuilder<UserAccountVO> userAccountSearch;
public UserAccountDaoImpl() {
userAccountSearch = createSearchBuilder();
userAccountSearch.and("apiKey", userAccountSearch.entity().getApiKey(), SearchCriteria.Op.EQ);
userAccountSearch.done();
}
@Override
public List<UserAccountVO> getAllUsersByNameAndEntity(String username, String entity) {
if (username == null) {
@ -79,12 +70,4 @@ public class UserAccountDaoImpl extends GenericDaoBase<UserAccountVO, Long> impl
}
return false;
}
@Override
public UserAccount getUserByApiKey(String apiKey) {
SearchCriteria<UserAccountVO> sc = userAccountSearch.create();
sc.setParameters("apiKey", apiKey);
return findOneBy(sc);
}
}

View File

@ -37,13 +37,6 @@ public interface UserDao extends GenericDao<UserVO, Long> {
List<UserVO> listByAccount(long accountId);
/**
* Finds a user based on the secret key provided.
* @param secretKey
* @return
*/
UserVO findUserBySecretKey(String secretKey);
/**
* Finds a user based on the registration token provided.
* @param registrationToken

View File

@ -65,10 +65,6 @@ public class UserDaoImpl extends GenericDaoBase<UserVO, Long> implements UserDao
UserIdSearch.and("id", UserIdSearch.entity().getId(), SearchCriteria.Op.EQ);
UserIdSearch.done();
SecretKeySearch = createSearchBuilder();
SecretKeySearch.and("secretKey", SecretKeySearch.entity().getSecretKey(), SearchCriteria.Op.EQ);
SecretKeySearch.done();
RegistrationTokenSearch = createSearchBuilder();
RegistrationTokenSearch.and("registrationToken", RegistrationTokenSearch.entity().getRegistrationToken(), SearchCriteria.Op.EQ);
RegistrationTokenSearch.done();
@ -121,13 +117,6 @@ public class UserDaoImpl extends GenericDaoBase<UserVO, Long> implements UserDao
return listBy(sc);
}
@Override
public UserVO findUserBySecretKey(String secretKey) {
SearchCriteria<UserVO> sc = SecretKeySearch.create();
sc.setParameters("secretKey", secretKey);
return findOneBy(sc);
}
@Override
public UserVO findUserByRegistrationToken(String registrationToken) {
SearchCriteria<UserVO> sc = RegistrationTokenSearch.create();

View File

@ -846,15 +846,18 @@ public class VMInstanceDaoImpl extends GenericDaoBase<VMInstanceVO, Long> implem
try {
pstmtLegacy = txn.prepareAutoCloseStatement(finalQueryLegacy.toString());
pstmt = txn.prepareAutoCloseStatement(finalQuery.toString());
for (int i = 0; i < resourceIdList.size(); i++) {
pstmtLegacy.setLong(1 + i, resourceIdList.get(i));
pstmt.setLong(1 + i, resourceIdList.get(i));
}
ResultSet rs = pstmtLegacy.executeQuery();
while (rs.next()) {
result.put(rs.getString(1).concat(rs.getString(2)), rs.getLong(3));
}
pstmt = txn.prepareAutoCloseStatement(finalQuery.toString());
for (int i = 0; i < resourceIdList.size(); i++) {
pstmt.setLong(1 + i, resourceIdList.get(i));
}
rs = pstmt.executeQuery();
while (rs.next()) {
result.put(rs.getString(1).concat(rs.getString(2)), rs.getLong(3));

View File

@ -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 org.apache.cloudstack.acl;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table(name = "api_keypair_permissions")
public class ApiKeyPairPermissionVO extends RolePermissionBaseVO implements ApiKeyPairPermission {
@Column(name = "api_keypair_id")
private long apiKeyPairId;
@Column(name = "sort_order")
private long sortOrder = 0;
public ApiKeyPairPermissionVO(long apiKeyPairId, String rule, Permission permission, String description) {
super(rule, permission, description);
this.apiKeyPairId = apiKeyPairId;
}
public ApiKeyPairPermissionVO(String rule, Permission permission, String description) {
super(rule, permission, description);
}
public ApiKeyPairPermissionVO() {
}
public long getApiKeyPairId() {
return this.apiKeyPairId;
}
public void setApiKeyPairId(long keyPairId) {
this.apiKeyPairId = keyPairId;
}
public void setSortOrder(long sortOrder) {
this.sortOrder = sortOrder;
}
public long getSortOrder() {
return sortOrder;
}
}

View File

@ -0,0 +1,244 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES 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 com.cloud.exception.InvalidParameterValueException;
import com.cloud.user.Account;
import com.cloud.utils.db.Encrypt;
import java.time.Instant;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
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 javax.persistence.Temporal;
import javax.persistence.TemporalType;
import java.util.Date;
import java.util.Objects;
import java.util.UUID;
import org.joda.time.DateTime;
@Entity
@Table(name = "api_keypair")
public class ApiKeyPairVO implements ApiKeyPair {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
@Column(name = "uuid", nullable = false)
private String uuid = UUID.randomUUID().toString();
@Column(name = "user_id", nullable = false)
private Long userId;
@Column(name = "name", nullable = false)
private String name;
@Column(name = "domain_id", nullable = false)
private Long domainId;
@Column(name = "account_id", nullable = false)
private Long accountId;
@Column(name = "start_date")
@Temporal(value = TemporalType.TIMESTAMP)
private Date startDate;
@Column(name = "end_date")
@Temporal(value = TemporalType.TIMESTAMP)
private Date endDate;
@Column(name = "created", nullable = false)
@Temporal(value = TemporalType.TIMESTAMP)
private Date created = Date.from(Instant.now());
@Column(name = "description")
private String description = "";
@Column(name = "api_key", nullable = false)
private String apiKey;
@Encrypt
@Column(name = "secret_key", nullable = false)
private String secretKey;
@Column(name = "removed")
@Temporal(value = TemporalType.TIMESTAMP)
private Date removed;
public ApiKeyPairVO() {
}
public ApiKeyPairVO(Long id) {
this.id = id;
}
public ApiKeyPairVO(Long userId, String description, Date startDate, Date endDate,
String apiKey, String secretKey) {
this.userId = userId;
this.description = description;
this.startDate = startDate;
this.endDate = endDate;
this.apiKey = apiKey;
this.secretKey = secretKey;
}
public ApiKeyPairVO(String name, Long userId, String description, Date startDate, Date endDate, Account account) {
this.name = Objects.requireNonNullElseGet(name, () -> userId + " - API key pair");
this.userId = userId;
this.description = description;
this.startDate = startDate;
this.endDate = endDate;
this.domainId = account.getDomainId();
this.accountId = account.getAccountId();
}
public ApiKeyPairVO(Long id, Long userId) {
this.id = id;
this.userId = userId;
}
public void validateDate() {
Date now = DateTime.now().toDate();
Date keypairStart = this.getStartDate();
Date keypairExpiration = this.getEndDate();
if (keypairStart != null && now.compareTo(keypairStart) <= 0) {
throw new InvalidParameterValueException(String.format("API key pair is not valid yet, start date: %s", keypairStart));
}
if (keypairExpiration != null && now.compareTo(keypairExpiration) >= 0) {
throw new InvalidParameterValueException(String.format("API key pair is expired, expiration date: %s", keypairExpiration));
}
}
public boolean hasEndDatePassed() {
Date now = DateTime.now().toDate();
Date keypairExpiration = this.getEndDate();
return keypairExpiration != null && now.compareTo(keypairExpiration) >= 0;
}
public long getId() {
return id;
}
public String getUuid() {
return uuid;
}
public Long getUserId() {
return userId;
}
public Date getStartDate() {
return startDate;
}
public Date getEndDate() {
return endDate;
}
public Date getCreated() {
return created;
}
public String getDescription() {
return description;
}
public String getApiKey() {
return apiKey;
}
public String getSecretKey() {
return secretKey;
}
public Class<?> getEntityType() {
return ApiKeyPair.class;
}
public String getName() {
return name;
}
public Date getRemoved() {
return removed;
}
@Override
public long getDomainId() {
return this.domainId;
}
@Override
public long getAccountId() {
return this.accountId;
}
public void setId(Long id) { this.id = id; }
public void setUuid(String uuid) {
this.uuid = uuid;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
public void setDescription(String description) {
this.description = description;
}
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public void setName(String name) {
this.name = name;
}
public void setDomainId(Long domainId) {
this.domainId = domainId;
}
public void setAccountId(Long accountId) {
this.accountId = accountId;
}
public void setCreated(Date created) {
this.created = created;
}
public void setRemoved(Date removed) {
this.removed = removed;
}
}

View File

@ -50,6 +50,12 @@ public class RolePermissionBaseVO implements RolePermissionEntity {
public RolePermissionBaseVO() { this.uuid = UUID.randomUUID().toString(); }
public RolePermissionBaseVO(final String rule, final Permission permission) {
this();
this.rule = rule;
this.permission = permission;
}
public RolePermissionBaseVO(final String rule, final Permission permission, final String description) {
this();
this.rule = rule;

View File

@ -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.acl.dao;
import com.cloud.utils.Pair;
import java.util.List;
import org.apache.cloudstack.acl.ApiKeyPairVO;
import com.cloud.utils.db.GenericDao;
import org.apache.cloudstack.api.command.admin.user.ListUserKeysCmd;
public interface ApiKeyPairDao extends GenericDao<ApiKeyPairVO, Long> {
ApiKeyPairVO findBySecretKey(String secretKey);
ApiKeyPairVO findByApiKey(String apiKey);
ApiKeyPairVO findByUuid(String uuid);
Pair<List<ApiKeyPairVO>, Integer> listApiKeysByUserOrApiKeyId(Long userId, Long apiKeyId);
ApiKeyPairVO getLastApiKeyCreatedByUser(Long userId);
Pair<List<ApiKeyPairVO>, Integer> listByUserIdsPaginated(List<Long> userIds, ListUserKeysCmd cmd);
}

View File

@ -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.
package org.apache.cloudstack.acl.dao;
import com.cloud.utils.Pair;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import java.util.List;
import org.apache.cloudstack.acl.ApiKeyPairVO;
import org.apache.cloudstack.api.command.admin.user.ListUserKeysCmd;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Component;
@Component
public class ApiKeyPairDaoImpl extends GenericDaoBase<ApiKeyPairVO, Long> implements ApiKeyPairDao {
private static final String ID = "id";
private static final String USER_ID = "userId";
private static final String API_KEY = "apiKey";
private static final String SECRET_KEY = "secretKey";
private final SearchBuilder<ApiKeyPairVO> keyPairSearch;
ApiKeyPairDaoImpl() {
super();
keyPairSearch = createSearchBuilder();
keyPairSearch.and(API_KEY, keyPairSearch.entity().getApiKey(), SearchCriteria.Op.EQ);
keyPairSearch.and(SECRET_KEY, keyPairSearch.entity().getSecretKey(), SearchCriteria.Op.EQ);
keyPairSearch.and(ID, keyPairSearch.entity().getId(), SearchCriteria.Op.EQ);
keyPairSearch.and(USER_ID, keyPairSearch.entity().getUserId(), SearchCriteria.Op.IN);
keyPairSearch.done();
}
@Override
public ApiKeyPairVO findByApiKey(String apiKey) {
SearchCriteria<ApiKeyPairVO> sc = keyPairSearch.create();
sc.setParameters(API_KEY, apiKey);
return findOneBy(sc);
}
public ApiKeyPairVO findBySecretKey(String secretKey) {
SearchCriteria<ApiKeyPairVO> sc = keyPairSearch.create();
sc.setParameters(SECRET_KEY, secretKey);
return findOneBy(sc);
}
public Pair<List<ApiKeyPairVO>, Integer> listApiKeysByUserOrApiKeyId(Long userId, Long apiKeyId) {
SearchCriteria<ApiKeyPairVO> sc = keyPairSearch.create();
sc.setParametersIfNotNull(USER_ID, userId);
sc.setParametersIfNotNull(ID, apiKeyId);
final Filter searchFilter = new Filter(100);
return searchAndCount(sc, searchFilter);
}
public ApiKeyPairVO getLastApiKeyCreatedByUser(Long userId) {
final SearchCriteria<ApiKeyPairVO> sc = keyPairSearch.create();
sc.setParametersIfNotNull(USER_ID, userId);
final Filter searchBySorted = new Filter(ApiKeyPairVO.class, ID, false, null, null);
return findOneBy(sc, searchBySorted);
}
public Pair<List<ApiKeyPairVO>, Integer> listByUserIdsPaginated(List<Long> userIds, ListUserKeysCmd cmd) {
Long pageSizeVal = cmd.getPageSizeVal();
Long startIndex = cmd.getStartIndex();
Filter searchFilter = new Filter(ApiKeyPairVO.class, ID, true, startIndex, pageSizeVal);
final SearchCriteria<ApiKeyPairVO> sc = keyPairSearch.create();
sc.setParameters(USER_ID, (Object[]) userIds.toArray(new Long[0]));
Pair<List<ApiKeyPairVO>, Integer> apiKeyPairVOList = searchAndCount(sc, searchFilter);
if (CollectionUtils.isEmpty(apiKeyPairVOList.first())) {
return new Pair<>(List.of(), 0);
}
return apiKeyPairVOList;
}
}

View File

@ -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 org.apache.cloudstack.acl.dao;
import com.cloud.utils.db.GenericDao;
import org.apache.cloudstack.acl.ApiKeyPairPermissionVO;
import java.util.List;
public interface ApiKeyPairPermissionsDao extends GenericDao<ApiKeyPairPermissionVO, Long> {
List<ApiKeyPairPermissionVO> findAllByApiKeyPairId(Long apiKeyPairId);
List<ApiKeyPairPermissionVO> findAllByKeyPairIdSorted(Long apiKeyPairId);
}

View File

@ -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 org.apache.cloudstack.acl.dao;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import java.util.Collections;
import java.util.Objects;
import org.apache.commons.collections.CollectionUtils;
import org.apache.cloudstack.acl.ApiKeyPairPermissionVO;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class ApiKeyPairPermissionsDaoImpl extends GenericDaoBase<ApiKeyPairPermissionVO, Long> implements ApiKeyPairPermissionsDao {
private static final String API_KEY_PAIR_ID = "apiKeyPairId";
private static final String SORT_ORDER = "sortOrder";
private final SearchBuilder<ApiKeyPairPermissionVO> permissionByApiKeyPairIdSearch;
public ApiKeyPairPermissionsDaoImpl() {
super();
permissionByApiKeyPairIdSearch = createSearchBuilder();
permissionByApiKeyPairIdSearch.and(API_KEY_PAIR_ID, permissionByApiKeyPairIdSearch.entity().getApiKeyPairId(), SearchCriteria.Op.EQ);
permissionByApiKeyPairIdSearch.done();
}
public List<ApiKeyPairPermissionVO> findAllByApiKeyPairId(Long apiKeyPairId) {
SearchCriteria<ApiKeyPairPermissionVO> sc = permissionByApiKeyPairIdSearch.create();
sc.setParameters(API_KEY_PAIR_ID, String.valueOf(apiKeyPairId));
return listBy(sc);
}
@Override
public ApiKeyPairPermissionVO persist(final ApiKeyPairPermissionVO item) {
item.setSortOrder(0);
final List<ApiKeyPairPermissionVO> permissionsList = findAllByKeyPairIdSorted(item.getApiKeyPairId());
if (!CollectionUtils.isEmpty(permissionsList)) {
ApiKeyPairPermissionVO lastPermission = permissionsList.get(permissionsList.size() - 1);
item.setSortOrder(lastPermission.getSortOrder() + 1);
}
return super.persist(item);
}
@Override
public List<ApiKeyPairPermissionVO> findAllByKeyPairIdSorted(Long apiKeyPairId) {
final SearchCriteria<ApiKeyPairPermissionVO> sc = permissionByApiKeyPairIdSearch.create();
sc.setParameters(API_KEY_PAIR_ID, apiKeyPairId);
final Filter searchBySorted = new Filter(ApiKeyPairPermissionVO.class, SORT_ORDER, true, null, null);
final List<ApiKeyPairPermissionVO> apiKeyPairPermissionList = listBy(sc, searchBySorted);
return Objects.requireNonNullElse(apiKeyPairPermissionList, Collections.emptyList());
}
}

View File

@ -77,7 +77,7 @@ public class ImageStoreDetailsDaoImpl extends ResourceDetailsDaoBase<ImageStoreD
for (ImageStoreDetailVO detail : details) {
String name = detail.getName();
String value = detail.getValue();
if (name.equals(ApiConstants.KEY) || name.equals(ApiConstants.S3_SECRET_KEY)) {
if (name.equals(ApiConstants.KEY) || name.equals(ApiConstants.SECRET_KEY)) {
value = DBEncryptionUtil.decrypt(value);
}
detailsMap.put(name, value);

View File

@ -310,6 +310,8 @@
<bean id="gpuDeviceDaoImpl" class="com.cloud.gpu.dao.GpuDeviceDaoImpl" />
<bean id="vgpuProfileDaoImpl" class="com.cloud.gpu.dao.VgpuProfileDaoImpl" />
<bean id="importVMTaskDaoImpl" class="com.cloud.vm.dao.ImportVMTaskDaoImpl" />
<bean id="apiKeyPairDaoImpl" class="org.apache.cloudstack.acl.dao.ApiKeyPairDaoImpl" />
<bean id="apiKeyPairPermissionsDaoImpl" class="org.apache.cloudstack.acl.dao.ApiKeyPairPermissionsDaoImpl" />
<bean id="dnsServerDao" class="org.apache.cloudstack.dns.dao.DnsServerDaoImpl" />
<bean id="dnsZoneDao" class="org.apache.cloudstack.dns.dao.DnsZoneDaoImpl" />
<bean id="dnsZoneNetworkMapDao" class="org.apache.cloudstack.dns.dao.DnsZoneNetworkMapDaoImpl" />

View File

@ -34,6 +34,19 @@ CREATE TABLE `cloud`.`backup_offering_details` (
UPDATE `cloud`.`configuration` SET value='random' WHERE name IN ('vm.allocation.algorithm', 'volume.allocation.algorithm') AND value='userconcentratedpod_random';
UPDATE `cloud`.`configuration` SET value='firstfit' WHERE name IN ('vm.allocation.algorithm', 'volume.allocation.algorithm') AND value='userconcentratedpod_firstfit';
-- Create kubernetes_cluster_affinity_group_map table for CKS per-node-type affinity groups
CREATE TABLE IF NOT EXISTS `cloud`.`kubernetes_cluster_affinity_group_map` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`cluster_id` bigint unsigned NOT NULL COMMENT 'kubernetes cluster id',
`node_type` varchar(32) NOT NULL COMMENT 'CONTROL, WORKER, or ETCD',
`affinity_group_id` bigint unsigned NOT NULL COMMENT 'affinity group id',
PRIMARY KEY (`id`),
CONSTRAINT `fk_kubernetes_cluster_ag_map__cluster_id` FOREIGN KEY (`cluster_id`) REFERENCES `kubernetes_cluster`(`id`) ON DELETE CASCADE,
CONSTRAINT `fk_kubernetes_cluster_ag_map__ag_id` FOREIGN KEY (`affinity_group_id`) REFERENCES `affinity_group`(`id`) ON DELETE CASCADE,
INDEX `i_kubernetes_cluster_ag_map__cluster_id`(`cluster_id`),
INDEX `i_kubernetes_cluster_ag_map__ag_id`(`affinity_group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- Create webhook_filter table
DROP TABLE IF EXISTS `cloud`.`webhook_filter`;
CREATE TABLE IF NOT EXISTS `cloud`.`webhook_filter` (
@ -50,6 +63,55 @@ CREATE TABLE IF NOT EXISTS `cloud`.`webhook_filter` (
CONSTRAINT `fk_webhook_filter__webhook_id` FOREIGN KEY(`webhook_id`) REFERENCES `webhook`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- "api_keypair" table for API and secret keys
CREATE TABLE IF NOT EXISTS `cloud`.`api_keypair` (
`id` bigint(20) unsigned NOT NULL auto_increment,
`uuid` varchar(40) UNIQUE NOT NULL,
`name` varchar(255) NOT NULL,
`domain_id` bigint(20) unsigned NOT NULL,
`account_id` bigint(20) unsigned NOT NULL,
`user_id` bigint(20) unsigned NOT NULL,
`start_date` datetime,
`end_date` datetime,
`description` varchar(100),
`api_key` varchar(255) NOT NULL,
`secret_key` varchar(255) NOT NULL,
`created` datetime NOT NULL,
`removed` datetime,
PRIMARY KEY (`id`),
CONSTRAINT `fk_api_keypair__user_id` FOREIGN KEY(`user_id`) REFERENCES `cloud`.`user`(`id`),
CONSTRAINT `fk_api_keypair__account_id` FOREIGN KEY(`account_id`) REFERENCES `cloud`.`account`(`id`),
CONSTRAINT `fk_api_keypair__domain_id` FOREIGN KEY(`domain_id`) REFERENCES `cloud`.`domain`(`id`)
);
-- "api_keypair_permissions" table for API key pairs permissions
CREATE TABLE IF NOT EXISTS `cloud`.`api_keypair_permissions` (
`id` bigint(20) unsigned NOT NULL auto_increment,
`uuid` varchar(40) UNIQUE,
`sort_order` bigint(20) unsigned NOT NULL DEFAULT 0,
`rule` varchar(255) NOT NULL,
`api_keypair_id` bigint(20) unsigned NOT NULL,
`permission` varchar(255) NOT NULL,
`description` varchar(255),
PRIMARY KEY (`id`),
CONSTRAINT `fk_keypair_permissions__api_keypair_id` FOREIGN KEY(`api_keypair_id`) REFERENCES `cloud`.`api_keypair`(`id`)
);
-- Populate "api_keypair" table with existing user API keys
INSERT INTO `cloud`.`api_keypair` (uuid, user_id, domain_id, account_id, api_key, secret_key, created, name)
SELECT UUID(), user.id, account.domain_id, account.id, user.api_key, user.secret_key, NOW(), 'Active key pair'
FROM `cloud`.`user` AS user
JOIN `cloud`.`account` AS account ON user.account_id = account.id
WHERE user.api_key IS NOT NULL AND user.secret_key IS NOT NULL;
-- Drop API keys from user table
ALTER TABLE `cloud`.`user` DROP COLUMN api_key, DROP COLUMN secret_key;
-- Grant access to the "deleteUserKeys" API to the "User", "Domain Admin" and "Resource Admin" roles, similarly to the "registerUserKeys" API
CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('User', 'deleteUserKeys', 'ALLOW');
CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Domain Admin', 'deleteUserKeys', 'ALLOW');
CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Resource Admin', 'deleteUserKeys', 'ALLOW');
-- ======================================================================
-- DNS Framework Schema

View File

@ -29,8 +29,6 @@ select
user.lastname,
user.email,
user.state,
user.api_key,
user.secret_key,
user.created,
user.removed,
user.timezone,

View File

@ -2565,8 +2565,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
throw new CloudRuntimeException("Destination storage pool with ID " + dataStore.getId() + " was not located.");
}
boolean isSrcAndDestPoolPowerFlexStorage = srcStoragePoolVO.getPoolType().equals(Storage.StoragePoolType.PowerFlex) && destStoragePoolVO.getPoolType().equals(Storage.StoragePoolType.PowerFlex);
if (srcStoragePoolVO.isManaged() && !isSrcAndDestPoolPowerFlexStorage && srcStoragePoolVO.getId() != destStoragePoolVO.getId()) {
if (srcStoragePoolVO.isManaged() && srcStoragePoolVO.getId() != destStoragePoolVO.getId()) {
throw new CloudRuntimeException("Migrating a volume online with KVM from managed storage is not currently supported.");
}

View File

@ -131,7 +131,7 @@ public class ImageStoreHelper {
String key = keyIter.next().toString();
String value = details.get(key);
// encrypt swift key or s3 secret key
if (key.equals(ApiConstants.KEY) || key.equals(ApiConstants.S3_SECRET_KEY)) {
if (key.equals(ApiConstants.KEY) || key.equals(ApiConstants.SECRET_KEY)) {
value = DBEncryptionUtil.encrypt(value);
}
ImageStoreDetailVO detail = new ImageStoreDetailVO(store.getId(), key, value, true);

View File

@ -420,6 +420,14 @@ public class ConfigKey<T> {
return value();
}
/**
* @deprecated
* Still used by some external code, but use {@link ConfigKey#valueInScope(Scope, Long)} instead.
*/
public T valueInDomain(Long domainId) {
return valueInScope(Scope.Domain, domainId);
}
public T valueInScope(Scope scope, Long id) {
if (id == null) {
return value();

View File

@ -17,15 +17,18 @@
package org.apache.cloudstack.acl;
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 java.util.stream.Collectors;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import org.apache.cloudstack.acl.RolePermissionEntity.Permission;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.utils.cache.LazyCache;
@ -47,7 +50,7 @@ public class DynamicRoleBasedAPIAccessChecker extends AdapterBase implements API
private RoleService roleService;
private List<PluggableService> services;
private Map<RoleType, Set<String>> annotationRoleBasedApisMap = new HashMap<RoleType, Set<String>>();
private Map<RoleType, Set<String>> annotationRoleBasedApisMap = new HashMap<>();
private LazyCache<Long, Account> accountCache;
private LazyCache<Long, Pair<Role, List<RolePermission>>> rolePermissionsCache;
@ -56,7 +59,7 @@ public class DynamicRoleBasedAPIAccessChecker extends AdapterBase implements API
protected DynamicRoleBasedAPIAccessChecker() {
super();
for (RoleType roleType : RoleType.values()) {
annotationRoleBasedApisMap.put(roleType, new HashSet<String>());
annotationRoleBasedApisMap.put(roleType, new HashSet<>());
}
}
@ -67,9 +70,12 @@ public class DynamicRoleBasedAPIAccessChecker extends AdapterBase implements API
}
List<RolePermission> allPermissions = roleService.findAllPermissionsBy(role.getId());
List<RolePermissionEntity> allPermissionEntities = allPermissions.stream().map(permission -> (RolePermissionEntity) permission)
.collect(Collectors.toList());
List<String> allowedApis = new ArrayList<>();
for (String api : apiNames) {
if (checkApiPermissionByRole(role, api, allPermissions)) {
if (checkApiPermissionByRole(role, api, allPermissionEntities, false)) {
allowedApis.add(api);
}
}
@ -84,8 +90,8 @@ public class DynamicRoleBasedAPIAccessChecker extends AdapterBase implements API
* @param allPermissions list of role permissions for the given role
* @return if the role has the permission for the API
*/
public boolean checkApiPermissionByRole(Role role, String apiName, List<RolePermission> allPermissions) {
for (final RolePermission permission : allPermissions) {
public boolean checkApiPermissionByRole(Role role, String apiName, List<RolePermissionEntity> allPermissions, boolean keyPairOverride) {
for (RolePermissionEntity permission : allPermissions) {
if (!permission.getRule().matches(apiName)) {
continue;
}
@ -94,13 +100,13 @@ public class DynamicRoleBasedAPIAccessChecker extends AdapterBase implements API
return false;
}
if (logger.isTraceEnabled()) {
logger.trace(String.format("The API [%s] is allowed for the role %s by the permission [%s].", apiName, role, permission.getRule().toString()));
}
logger.trace("The API [{}] is allowed for the role {} by the permission [{}].", apiName, role, permission.getRule().toString());
return true;
}
return annotationRoleBasedApisMap.get(role.getRoleType()) != null &&
annotationRoleBasedApisMap.get(role.getRoleType()).contains(apiName);
annotationRoleBasedApisMap.get(role.getRoleType()).contains(apiName) &&
!keyPairOverride;
}
protected Account getAccountFromId(long accountId) {
@ -135,49 +141,46 @@ public class DynamicRoleBasedAPIAccessChecker extends AdapterBase implements API
}
@Override
public boolean checkAccess(User user, String commandName) throws PermissionDeniedException {
public boolean checkAccess(User user, String commandName, ApiKeyPairPermission ... apiKeyPairPermissions) throws PermissionDeniedException {
if (!isEnabled()) {
return true;
}
Account account = getAccountFromIdUsingCache(user.getAccountId());
if (account == null) {
throw new PermissionDeniedException(String.format("Account for user id [%s] cannot be found", user.getUuid()));
throw new PermissionDeniedException(String.format("Account for user with ID [%s] cannot be found", user.getUuid()));
}
Pair<Role, List<RolePermission>> roleAndPermissions = getRolePermissionsUsingCache(account.getRoleId());
final Role accountRole = roleAndPermissions.first();
if (accountRole == null) {
throw new PermissionDeniedException(String.format("Account role for user id [%s] cannot be found.", user.getUuid()));
}
if (accountRole.getRoleType() == RoleType.Admin && accountRole.getId() == RoleType.Admin.getId()) {
logger.info("Account for user id {} is Root Admin or Domain Admin, all APIs are allowed.", user.getUuid());
return true;
}
List<RolePermission> allPermissions = roleAndPermissions.second();
if (checkApiPermissionByRole(accountRole, commandName, allPermissions)) {
return true;
}
throw new UnavailableCommandException(String.format("The API [%s] does not exist or is not available for the account for user id [%s].", commandName, user.getUuid()));
return checkAccess(account, commandName, apiKeyPairPermissions);
}
public boolean checkAccess(Account account, String commandName) {
@Override
public boolean checkAccess(Account account, String commandName, ApiKeyPairPermission ... apiKeyPairPermissions) {
Pair<Role, List<RolePermission>> roleAndPermissions = getRolePermissionsUsingCache(account.getRoleId());
final Role accountRole = roleAndPermissions.first();
if (accountRole == null) {
throw new PermissionDeniedException(String.format("The account [%s] has role null or unknown.", account));
}
if (accountRole.getRoleType() == RoleType.Admin && accountRole.getId() == RoleType.Admin.getId()) {
if (logger.isTraceEnabled()) {
logger.trace(String.format("Account [%s] is Root Admin or Domain Admin, all APIs are allowed.", account));
}
if (accountRole.getRoleType() == RoleType.Admin && accountRole.getId() == RoleType.Admin.getId() && apiKeyPairPermissions.length == 0) {
logger.info("Account [{}] is Root Admin and there aren't any API key pair permissions involved, thus, all APIs are allowed.", account);
return true;
}
List<RolePermission> allPermissions = roleService.findAllPermissionsBy(accountRole.getId());
if (checkApiPermissionByRole(accountRole, commandName, allPermissions)) {
boolean considerKeyPairPermissions = apiKeyPairPermissions.length > 0;
List<RolePermissionEntity> allRules = considerKeyPairPermissions ? Arrays.asList(apiKeyPairPermissions) : new ArrayList<>(roleAndPermissions.second());
if (checkApiPermissionByRole(accountRole, commandName, allRules, considerKeyPairPermissions)) {
return true;
}
throw new UnavailableCommandException(String.format("The API [%s] does not exist or is not available for the account %s.", commandName, account));
throw new UnavailableCommandException(String.format("The API [%s] does not exist or is not available for the account %s.", commandName, account.getAccountName()));
}
@Override
public List<RolePermissionEntity> getImplicitRolePermissions(RoleType roleType) {
return annotationRoleBasedApisMap.get(roleType)
.stream()
.map(implicitApi -> new RolePermissionBaseVO(implicitApi, Permission.ALLOW))
.collect(Collectors.toList());
}
/**

View File

@ -22,6 +22,8 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import com.cloud.exception.UnavailableCommandException;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@ -195,4 +197,68 @@ public class DynamicRoleBasedAPIAccessCheckerTest extends TestCase {
List<String> apisReceived = apiAccessCheckerSpy.getApisAllowedToUser(getTestRole(), getTestUser(), apiNames);
Assert.assertEquals(0, apisReceived.size());
}
@Test(expected = UnavailableCommandException.class)
public void checkAccessTestInvalidApiKeyPairPermission() {
final String api = "someDeniedApi";
final ApiKeyPairPermission permission = new ApiKeyPairPermissionVO(1L, api, Permission.DENY, null);
assertFalse(apiAccessCheckerSpy.checkAccess(getTestUser(), api, permission));
}
@Test(expected = UnavailableCommandException.class)
public void checkAccessTestUnrelatedApiKeyPairPermission() {
final String api = "someDeniedApi";
final ApiKeyPairPermission permission = new ApiKeyPairPermissionVO(1L, "apiName", Permission.ALLOW, null);
assertFalse(apiAccessCheckerSpy.checkAccess(getTestUser(), api, permission));
}
@Test
public void checkAccessTestValidApiKeyPairPermission() {
final String api = "someAllowedApi";
final ApiKeyPairPermission permission = new ApiKeyPairPermissionVO(1L, api, Permission.ALLOW, null);
assertTrue(apiAccessCheckerSpy.checkAccess(getTestUser(), api, permission));
}
@Test
public void checkAccessTestValidMultipleApiKeyPermissions() {
final String api = "someAllowedApi";
final ApiKeyPairPermission[] permissions = new ApiKeyPairPermission[]{
new ApiKeyPairPermissionVO(1L, "someDeniedApi", Permission.DENY, null),
new ApiKeyPairPermissionVO(1L, api, Permission.ALLOW, null)
};
assertTrue(apiAccessCheckerSpy.checkAccess(getTestUser(), api, permissions));
}
@Test(expected = UnavailableCommandException.class)
public void checkAccessTestInvalidMultipleApiKeyPermissions() {
final String api = "someDeniedApi";
final ApiKeyPairPermission[] permissions = new ApiKeyPairPermission[]{
new ApiKeyPairPermissionVO(1L, "someAllowedApi", Permission.ALLOW, null),
new ApiKeyPairPermissionVO(1L, api, Permission.DENY, null)
};
assertFalse(apiAccessCheckerSpy.checkAccess(getTestUser(), api, permissions));
}
@Test
public void checkAccessTestValidApiKeyPairPermissionWithNullOverride() {
final String api = "someAllowedApi";
final ApiKeyPairPermission[] emptyPermissionArray = List.of().toArray(new ApiKeyPairPermission[0]);
final RolePermission permission = new RolePermissionVO(1L, api, Permission.ALLOW, null);
Mockito.doReturn(Collections.singletonList(permission)).when(roleServiceMock).findAllPermissionsBy(Mockito.anyLong());
assertTrue(apiAccessCheckerSpy.checkAccess(getTestUser(), api, emptyPermissionArray));
Mockito.verify(roleServiceMock).findAllPermissionsBy(Mockito.anyLong());
}
@Test(expected = UnavailableCommandException.class)
public void checkAccessTestInvalidApiKeyPairPermissionWithNullOverride() {
final String api = "someDeniedApi";
final ApiKeyPairPermission[] emptyPermissionArray = List.of().toArray(new ApiKeyPairPermission[0]);
final RolePermission permission = new RolePermissionVO(1L, api, Permission.DENY, null);
Mockito.doReturn(Collections.singletonList(permission)).when(roleServiceMock).findAllPermissionsBy(Mockito.anyLong());
assertTrue(apiAccessCheckerSpy.checkAccess(getTestUser(), api, emptyPermissionArray));
Mockito.verify(roleServiceMock, Mockito.times(1)).findAllPermissionsBy(Mockito.anyLong());
}
}

View File

@ -23,6 +23,7 @@ import javax.inject.Inject;
import javax.naming.ConfigurationException;
import org.apache.cloudstack.acl.RolePermissionEntity.Permission;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import org.apache.cloudstack.context.CallContext;
import com.cloud.exception.PermissionDeniedException;
@ -105,7 +106,7 @@ public class ProjectRoleBasedApiAccessChecker extends AdapterBase implements AP
}
@Override
public boolean checkAccess(User user, String apiCommandName) throws PermissionDeniedException {
public boolean checkAccess(User user, String apiCommandName, ApiKeyPairPermission... apiKeyPairPermissions) throws PermissionDeniedException {
if (!isEnabled()) {
return true;
}
@ -150,7 +151,7 @@ public class ProjectRoleBasedApiAccessChecker extends AdapterBase implements AP
}
@Override
public boolean checkAccess(Account account, String apiCommandName) throws PermissionDeniedException {
public boolean checkAccess(Account account, String apiCommandName, ApiKeyPairPermission... apiKeyPairPermissions) throws PermissionDeniedException {
return true;
}
@ -182,6 +183,11 @@ public class ProjectRoleBasedApiAccessChecker extends AdapterBase implements AP
return true;
}
@Override
public List<RolePermissionEntity> getImplicitRolePermissions(RoleType roleType) {
return List.of();
}
@Override
public boolean start() {
return super.start();

View File

@ -21,11 +21,13 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.exception.UnavailableCommandException;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import org.apache.cloudstack.api.APICommand;
@ -90,7 +92,7 @@ public class StaticRoleBasedAPIAccessChecker extends AdapterBase implements APIA
}
@Override
public boolean checkAccess(User user, String commandName) throws PermissionDeniedException {
public boolean checkAccess(User user, String commandName, ApiKeyPairPermission... apiKeyPairPermissions) throws PermissionDeniedException {
if (!isEnabled()) {
return true;
}
@ -104,7 +106,7 @@ public class StaticRoleBasedAPIAccessChecker extends AdapterBase implements APIA
}
@Override
public boolean checkAccess(Account account, String commandName) {
public boolean checkAccess(Account account, String commandName, ApiKeyPairPermission... apiKeyPairPermissions) {
if (!isEnabled()) {
return true;
}
@ -163,6 +165,14 @@ public class StaticRoleBasedAPIAccessChecker extends AdapterBase implements APIA
return super.start();
}
@Override
public List<RolePermissionEntity> getImplicitRolePermissions(RoleType roleType) {
return annotationRoleBasedApisMap.get(roleType)
.stream()
.map(implicitApi -> new RolePermissionBaseVO(implicitApi, RolePermissionEntity.Permission.ALLOW))
.collect(Collectors.toList());
}
private void processMapping(Map<String, String> configMap) {
for (Map.Entry<String, String> entry : configMap.entrySet()) {
String apiName = entry.getKey();

View File

@ -39,6 +39,12 @@
<artifactId>cloud-utils</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-plugin-api-limit-account-based</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>

View File

@ -52,7 +52,7 @@ public class ListApisCmd extends BaseCmd {
public void execute() throws ServerApiException {
if (_apiDiscoveryService != null) {
User user = CallContext.current().getCallingUser();
ListResponse<ApiDiscoveryResponse> response = (ListResponse<ApiDiscoveryResponse>)_apiDiscoveryService.listApis(user, name);
ListResponse<ApiDiscoveryResponse> response = (ListResponse<ApiDiscoveryResponse>)_apiDiscoveryService.listApis(user, name, this);
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");
}

View File

@ -18,6 +18,7 @@ package org.apache.cloudstack.discovery;
import com.cloud.user.Account;
import org.apache.cloudstack.api.BaseResponse;
import org.apache.cloudstack.api.command.user.discovery.ListApisCmd;
import org.apache.cloudstack.api.response.ListResponse;
import com.cloud.user.User;
@ -28,5 +29,5 @@ import java.util.List;
public interface ApiDiscoveryService extends PluggableService {
List<String> listApiNames(Account account);
ListResponse<? extends BaseResponse> listApis(User user, String apiName);
ListResponse<? extends BaseResponse> listApis(User user, String apiName, ListApisCmd cmd);
}

View File

@ -26,6 +26,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.inject.Inject;
@ -33,6 +34,8 @@ import org.apache.cloudstack.acl.APIChecker;
import org.apache.cloudstack.acl.Role;
import org.apache.cloudstack.acl.RoleService;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.RolePermissionEntity;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairService;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.BaseAsyncCmd;
import org.apache.cloudstack.api.BaseAsyncCreateCmd;
@ -44,6 +47,7 @@ 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.apache.cloudstack.ratelimit.ApiRateLimitService;
import org.apache.cloudstack.resourcedetail.UserDetailVO;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import org.apache.commons.collections.CollectionUtils;
@ -78,6 +82,9 @@ public class ApiDiscoveryServiceImpl extends ComponentLifecycleBase implements A
@Inject
RoleService roleService;
@Inject
ApiKeyPairService apiKeyPairService;
protected ApiDiscoveryServiceImpl() {
super();
}
@ -257,16 +264,24 @@ public class ApiDiscoveryServiceImpl extends ComponentLifecycleBase implements A
}
@Override
public ListResponse<? extends BaseResponse> listApis(User user, String name) {
public ListResponse<? extends BaseResponse> listApis(User user, String name, ListApisCmd cmd) {
ListResponse<ApiDiscoveryResponse> response = new ListResponse<>();
List<ApiDiscoveryResponse> responseList = new ArrayList<>();
List<String> apisAllowed = new ArrayList<>(s_apiNameDiscoveryResponseMap.keySet());
String apikey = accountService.getAccessingApiKey(cmd);
if (user == null)
return null;
Account account = accountService.getAccount(user.getAccountId());
if (name != null) {
Account account = accountService.getAccount(user.getAccountId());
if (account == null) {
throw new PermissionDeniedException(String.format("The account with id [%s] for user [%s] is null.", user.getAccountId(), user));
}
Role role = roleService.findRole(account.getRoleId());
if (apikey != null) {
responseList = listApisForKeyPair(apikey, name, user, role, apisAllowed);
} else if (name != null) {
if (!s_apiNameDiscoveryResponseMap.containsKey(name))
return null;
@ -281,11 +296,6 @@ public class ApiDiscoveryServiceImpl extends ComponentLifecycleBase implements A
responseList.add(getApiDiscoveryResponseWithAccessibleParams(name, account));
} else {
if (account == null) {
throw new PermissionDeniedException(String.format("The account with id [%s] for user [%s] is null.", user.getAccountId(), user));
}
final Role role = roleService.findRole(account.getRoleId());
if (role == null || role.getId() < 1L) {
throw new PermissionDeniedException(String.format("The account [%s] has role null or unknown.",
ReflectionToStringBuilderUtils.reflectOnlySelectedFields(account, "accountName", "uuid")));
@ -343,6 +353,44 @@ public class ApiDiscoveryServiceImpl extends ComponentLifecycleBase implements A
return cmdList;
}
protected List<ApiDiscoveryResponse> listApisForKeyPair(String apiKey, String apiName, User user, Role role, List<String> apisAllowed) {
List<RolePermissionEntity> keyPairPermissions = accountService.getAllKeypairPermissions(apiKey);
List<String> filteredApis = new ArrayList<>();
if (apiName != null && isApiAllowedForKey(keyPairPermissions, apiName)) {
filteredApis = List.of(apiName);
} else {
for (String api : apisAllowed) {
if (isApiAllowedForKey(keyPairPermissions, api)) {
filteredApis.add(api);
}
}
}
checkRateLimit(user, role, filteredApis);
return filteredApis.stream().map(api -> s_apiNameDiscoveryResponseMap.get(api)).collect(Collectors.toList());
}
protected boolean isApiAllowedForKey(List<RolePermissionEntity> rolePermissionEntities, String apiName) {
for (RolePermissionEntity rolePermissionEntity : rolePermissionEntities) {
if (!rolePermissionEntity.getRule().matches(apiName)) {
continue;
}
return rolePermissionEntity.getPermission().equals(RolePermissionEntity.Permission.ALLOW);
}
return false;
}
private void checkRateLimit(User user, Role role, List<String> apiNames) {
for (APIChecker apiChecker : _apiAccessCheckers) {
if (!(apiChecker instanceof ApiRateLimitService)) {
continue;
}
apiChecker.getApisAllowedToUser(role, user, apiNames);
return;
}
}
public List<APIChecker> getApiAccessCheckers() {
return _apiAccessCheckers;
}

View File

@ -99,7 +99,7 @@ public class ApiDiscoveryTest {
@Test (expected = PermissionDeniedException.class)
public void listApisTestThrowPermissionDeniedExceptionOnAccountNull() throws PermissionDeniedException {
Mockito.when(accountServiceMock.getAccount(Mockito.anyLong())).thenReturn(null);
discoveryServiceSpy.listApis(getTestUser(), null);
discoveryServiceSpy.listApis(getTestUser(), null, null);
}
@Test (expected = PermissionDeniedException.class)
@ -107,7 +107,7 @@ public class ApiDiscoveryTest {
Mockito.when(accountServiceMock.getAccount(Mockito.anyLong())).thenReturn(getNormalAccount());
Mockito.when(roleServiceMock.findRole(Mockito.anyLong())).thenReturn(null);
discoveryServiceSpy.listApis(getTestUser(), null);
discoveryServiceSpy.listApis(getTestUser(), null, null);
}
@Test (expected = PermissionDeniedException.class)
@ -117,7 +117,7 @@ public class ApiDiscoveryTest {
Mockito.when(accountServiceMock.getAccount(Mockito.anyLong())).thenReturn(getNormalAccount());
Mockito.when(roleServiceMock.findRole(Mockito.anyLong())).thenReturn(unknownRoleVO);
discoveryServiceSpy.listApis(getTestUser(), null);
discoveryServiceSpy.listApis(getTestUser(), null, null);
}
@Test
@ -128,7 +128,7 @@ public class ApiDiscoveryTest {
Mockito.when(accountServiceMock.getAccount(Mockito.anyLong())).thenReturn(adminAccountVO);
Mockito.when(roleServiceMock.findRole(Mockito.anyLong())).thenReturn(adminRoleVO);
discoveryServiceSpy.listApis(getTestUser(), null);
discoveryServiceSpy.listApis(getTestUser(), null, null);
Mockito.verify(apiCheckerMock, Mockito.times(0)).getApisAllowedToUser(any(Role.class), any(User.class), anyList());
}
@ -140,7 +140,7 @@ public class ApiDiscoveryTest {
Mockito.when(accountServiceMock.getAccount(Mockito.anyLong())).thenReturn(getNormalAccount());
Mockito.when(roleServiceMock.findRole(Mockito.anyLong())).thenReturn(userRoleVO);
discoveryServiceSpy.listApis(getTestUser(), null);
discoveryServiceSpy.listApis(getTestUser(), null, null);
Mockito.verify(apiCheckerMock, Mockito.times(1)).getApisAllowedToUser(any(Role.class), any(User.class), anyList());
}
@ -153,7 +153,7 @@ public class ApiDiscoveryTest {
Mockito.when(mockUserAccount.getDetails()).thenReturn(userDetails);
Mockito.when(accountServiceMock.getAccount(Mockito.anyLong())).thenReturn(getNormalAccount());
Mockito.when(roleServiceMock.findRole(Mockito.anyLong())).thenReturn(userRoleVO);
discoveryServiceSpy.listApis(getTestUser(), null);
discoveryServiceSpy.listApis(getTestUser(), null, null);
Mockito.verify(apiCheckerMock, Mockito.times(1)).getApisAllowedToUser(any(Role.class), any(User.class), anyList());
}
@ -166,7 +166,7 @@ public class ApiDiscoveryTest {
Mockito.when(accountServiceMock.getAccount(Mockito.anyLong())).thenReturn(getNormalAccount());
Mockito.when(roleServiceMock.findRole(Mockito.anyLong())).thenReturn(userRoleVO);
Mockito.when(apiNameDiscoveryResponseMapMock.get(Mockito.anyString())).thenReturn(Mockito.mock(ApiDiscoveryResponse.class));
ListResponse<ApiDiscoveryResponse> response = (ListResponse<ApiDiscoveryResponse>) discoveryServiceSpy.listApis(getTestUser(), null);
ListResponse<ApiDiscoveryResponse> response = (ListResponse<ApiDiscoveryResponse>) discoveryServiceSpy.listApis(getTestUser(), null, null);
Assert.assertEquals(4, response.getResponses().size());
}
}

View File

@ -27,6 +27,9 @@ import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import org.apache.cloudstack.acl.Role;
import org.apache.cloudstack.acl.RolePermissionEntity;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import org.springframework.stereotype.Component;
@ -161,17 +164,17 @@ public class ApiRateLimitServiceImpl extends AdapterBase implements APIChecker,
}
@Override
public boolean checkAccess(User user, String apiCommandName) throws PermissionDeniedException {
public boolean checkAccess(User user, String apiCommandName, ApiKeyPairPermission ... apiKeyPairPermissions) throws PermissionDeniedException {
if (!isEnabled()) {
return true;
}
Account account = _accountService.getAccount(user.getAccountId());
return checkAccess(account, apiCommandName);
return checkAccess(account, apiCommandName, apiKeyPairPermissions);
}
@Override
public boolean checkAccess(Account account, String commandName) {
public boolean checkAccess(Account account, String commandName, ApiKeyPairPermission ... apiKeyPairPermissions) {
Long accountId = account.getAccountId();
if (_accountService.isRootAdmin(accountId)) {
logger.info(String.format("Account [%s] is Root Admin, in this case, API limit does not apply.",
@ -207,6 +210,11 @@ public class ApiRateLimitServiceImpl extends AdapterBase implements APIChecker,
return true;
}
@Override
public List<RolePermissionEntity> getImplicitRolePermissions(RoleType roleType) {
return List.of();
}
@Override
public boolean isEnabled() {
if (!enabled) {

View File

@ -252,7 +252,7 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
}
public boolean isUserAllowedToSeeActivationRules(User user) {
List<ApiDiscoveryResponse> apiList = (List<ApiDiscoveryResponse>) apiDiscoveryService.listApis(user, null).getResponses();
List<ApiDiscoveryResponse> apiList = (List<ApiDiscoveryResponse>) apiDiscoveryService.listApis(user, null, null).getResponses();
return apiList.stream().anyMatch(response -> StringUtils.equalsAny(response.getName(), "quotaTariffCreate", "quotaTariffUpdate"));
}

View File

@ -652,7 +652,7 @@ public class QuotaResponseBuilderImplTest extends TestCase {
ListResponse<ApiDiscoveryResponse> responseList = new ListResponse<>();
responseList.setResponses(cmdList);
Mockito.doReturn(responseList).when(discoveryServiceMock).listApis(userMock, null);
Mockito.doReturn(responseList).when(discoveryServiceMock).listApis(userMock, null, null);
assertTrue(quotaResponseBuilderSpy.isUserAllowedToSeeActivationRules(userMock));
}
@ -668,7 +668,7 @@ public class QuotaResponseBuilderImplTest extends TestCase {
ListResponse<ApiDiscoveryResponse> responseList = new ListResponse<>();
responseList.setResponses(cmdList);
Mockito.doReturn(responseList).when(discoveryServiceMock).listApis(userMock, null);
Mockito.doReturn(responseList).when(discoveryServiceMock).listApis(userMock, null, null);
assertTrue(quotaResponseBuilderSpy.isUserAllowedToSeeActivationRules(userMock));
}
@ -684,7 +684,7 @@ public class QuotaResponseBuilderImplTest extends TestCase {
ListResponse<ApiDiscoveryResponse> responseList = new ListResponse<>();
responseList.setResponses(cmdList);
Mockito.doReturn(responseList).when(discoveryServiceMock).listApis(userMock, null);
Mockito.doReturn(responseList).when(discoveryServiceMock).listApis(userMock, null, null);
assertFalse(quotaResponseBuilderSpy.isUserAllowedToSeeActivationRules(userMock));
}

View File

@ -263,9 +263,8 @@ public class BaremetalVlanManagerImpl extends ManagerBase implements BaremetalVl
user.setSource(User.Source.UNKNOWN);
user = userDao.persist(user);
String[] keys = acntMgr.createApiKeyAndSecretKey(user.getId());
user.setApiKey(keys[0]);
user.setSecretKey(keys[1]);
acntMgr.createApiKeyAndSecretKey(user.getId());
userDao.update(user.getId(), user);
return true;
}

View File

@ -884,7 +884,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
protected StorageSubsystemCommandHandler storageHandler;
private boolean convertInstanceVerboseMode = false;
private String[] convertInstanceEnv = null;
private Map<String, String> convertInstanceEnv = null;
protected boolean dpdkSupport = false;
protected String dpdkOvsPath;
protected String directDownloadTemporaryDownloadPath;
@ -949,7 +949,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
return convertInstanceVerboseMode;
}
public String[] getConvertInstanceEnv() {
public Map<String, String> getConvertInstanceEnv() {
return convertInstanceEnv;
}
@ -1439,14 +1439,14 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
return;
}
if (StringUtils.isNotBlank(convertEnvTmpDir) && StringUtils.isNotBlank(convertEnvVirtv2vTmpDir)) {
convertInstanceEnv = new String[2];
convertInstanceEnv[0] = String.format("%s=%s", "TMPDIR", convertEnvTmpDir);
convertInstanceEnv[1] = String.format("%s=%s", "VIRT_V2V_TMPDIR", convertEnvVirtv2vTmpDir);
convertInstanceEnv = new HashMap<>(2);
convertInstanceEnv.put("TMPDIR", convertEnvTmpDir);
convertInstanceEnv.put("VIRT_V2V_TMPDIR", convertEnvVirtv2vTmpDir);
} else {
convertInstanceEnv = new String[1];
convertInstanceEnv = new HashMap<>(1);
String key = StringUtils.isNotBlank(convertEnvTmpDir) ? "TMPDIR" : "VIRT_V2V_TMPDIR";
String value = StringUtils.isNotBlank(convertEnvTmpDir) ? convertEnvTmpDir : convertEnvVirtv2vTmpDir;
convertInstanceEnv[0] = String.format("%s=%s", key, value);
convertInstanceEnv.put(key, value);
}
}

View File

@ -22,9 +22,11 @@ import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import com.cloud.agent.api.Answer;
@ -244,7 +246,12 @@ public class LibvirtConvertInstanceCommandWrapper extends CommandWrapper<Convert
String logPrefix = String.format("(%s) virt-v2v ovf source: %s progress", originalVMName, sourceOVFDirPath);
OutputInterpreter.LineByLineOutputLogger outputLogger = new OutputInterpreter.LineByLineOutputLogger(logger, logPrefix);
script.execute(outputLogger);
Map<String, String> convertInstanceEnv = serverResource.getConvertInstanceEnv();
if (MapUtils.isEmpty(convertInstanceEnv)) {
script.execute(outputLogger);
} else {
script.execute(outputLogger, convertInstanceEnv);
}
int exitValue = script.getExitValue();
return exitValue == 0;
}

View File

@ -180,7 +180,8 @@ public class ScaleIOStorageAdaptorTest {
details.put(ScaleIOGatewayClient.STORAGE_POOL_MDMS, "1.1.1.1,2.2.2.2");
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl status scini"))).thenReturn(3);
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-enabled scini"))).thenReturn(0);
when(Script.runSimpleBashScript(Mockito.eq("/opt/emc/scaleio/sdc/bin/drv_cfg --query_mdms|grep 1.1.1.1"))).thenReturn("MDM-ID 71fd458f0775010f SDC ID 4421a91a00000000 INSTALLATION ID 204930df2cbcaf8e IPs [0]-3.3.3.3 [1]-4.4.4.4");
when(Script.runSimpleBashScript(Mockito.eq("/opt/emc/scaleio/sdc/bin/drv_cfg --query_mdms|grep 1.1.1.1"), Mockito.eq(ScaleIOUtil.DEFAULT_TIMEOUT_MS)))
.thenReturn("MDM-ID 71fd458f0775010f SDC ID 4421a91a00000000 INSTALLATION ID 204930df2cbcaf8e IPs [0]-3.3.3.3 [1]-4.4.4.4");
Pair<Boolean, String> result = scaleIOStorageAdaptor.unprepareStorageClient(poolUuid, details);
@ -196,11 +197,11 @@ public class ScaleIOStorageAdaptorTest {
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-enabled scini"))).thenReturn(0);
when(Script.executeCommand(Mockito.eq("sed -i '/1.1.1.1\\,/d' /etc/emc/scaleio/drv_cfg.txt"))).thenReturn(new Pair<>(null, null));
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl restart scini"))).thenReturn(0);
when(Script.runSimpleBashScript(Mockito.eq("/opt/emc/scaleio/sdc/bin/drv_cfg --query_mdms|grep 1.1.1.1"))).thenReturn("MDM-ID 71fd458f0775010f SDC ID 4421a91a00000000 INSTALLATION ID 204930df2cbcaf8e IPs [0]-1.1.1.1 [1]-2.2.2.2");
when(Script.runSimpleBashScript(Mockito.eq("/opt/emc/scaleio/sdc/bin/drv_cfg --query_mdms --file /etc/emc/scaleio/drv_cfg.txt|grep 1.1.1.1"), Mockito.eq(ScaleIOUtil.DEFAULT_TIMEOUT_MS)))
.thenReturn("MDM-ID 71fd458f0775010f SDC ID 4421a91a00000000 INSTALLATION ID 204930df2cbcaf8e IPs [0]-1.1.1.1 [1]-2.2.2.2");
when(Script.executeCommand(Mockito.eq("/opt/emc/scaleio/sdc/bin/drv_cfg"))).thenReturn(new Pair<>(null, null));
when(Script.executeCommand(Mockito.eq("/opt/emc/scaleio/sdc/bin/drv_cfg --query_vols"))).thenReturn(new Pair<>("", null));
Pair<Boolean, String> result = scaleIOStorageAdaptor.unprepareStorageClient(poolUuid, details);
Assert.assertFalse(result.first());

View File

@ -95,9 +95,8 @@ public class ScaleIOStoragePoolTest {
details.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, systemId);
try (MockedStatic<Script> ignored = Mockito.mockStatic(Script.class)) {
when(Script.runSimpleBashScript(
"/opt/emc/scaleio/sdc/bin/drv_cfg --query_mdms|grep 218ce1797566a00f|awk '{print $5}'")).thenReturn(
sdcId);
when(Script.runSimpleBashScript(Mockito.eq("/opt/emc/scaleio/sdc/bin/drv_cfg --query_mdms --file /etc/emc/scaleio/drv_cfg.txt|grep 218ce1797566a00f|awk '{print $5}'"), Mockito.eq(ScaleIOUtil.DEFAULT_TIMEOUT_MS)))
.thenReturn(sdcId);
ScaleIOStoragePool pool1 = new ScaleIOStoragePool(uuid, "192.168.1.19", 443, "a519be2f00000000", type,
details, adapter);
@ -116,10 +115,10 @@ public class ScaleIOStoragePoolTest {
details.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, systemId);
try (MockedStatic<Script> ignored = Mockito.mockStatic(Script.class)) {
when(Script.runSimpleBashScript(
"/opt/emc/scaleio/sdc/bin/drv_cfg --query_mdms|grep 218ce1797566a00f|awk '{print $5}'")).thenReturn(
null);
when(Script.runSimpleBashScript("/opt/emc/scaleio/sdc/bin/drv_cfg --query_guid")).thenReturn(sdcGuid);
when(Script.runSimpleBashScript(Mockito.eq("/opt/emc/scaleio/sdc/bin/drv_cfg --query_mdms --file /etc/emc/scaleio/drv_cfg.txt|grep 218ce1797566a00f|awk '{print $5}'"), Mockito.eq(ScaleIOUtil.DEFAULT_TIMEOUT_MS)))
.thenReturn(null);
when(Script.runSimpleBashScript(Mockito.eq("/opt/emc/scaleio/sdc/bin/drv_cfg --query_guid --file /etc/emc/scaleio/drv_cfg.txt"), Mockito.eq(ScaleIOUtil.DEFAULT_TIMEOUT_MS)))
.thenReturn(sdcGuid);
ScaleIOStoragePool pool1 = new ScaleIOStoragePool(uuid, "192.168.1.19", 443, "a519be2f00000000", type,
details, adapter);

View File

@ -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.kubernetes.cluster;
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 = "kubernetes_cluster_affinity_group_map")
public class KubernetesClusterAffinityGroupMapVO implements InternalIdentity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "cluster_id")
private long clusterId;
@Column(name = "node_type")
private String nodeType;
@Column(name = "affinity_group_id")
private long affinityGroupId;
public KubernetesClusterAffinityGroupMapVO() {
}
public KubernetesClusterAffinityGroupMapVO(long clusterId, String nodeType, long affinityGroupId) {
this.clusterId = clusterId;
this.nodeType = nodeType;
this.affinityGroupId = affinityGroupId;
}
@Override
public long getId() {
return id;
}
public long getClusterId() {
return clusterId;
}
public void setClusterId(long clusterId) {
this.clusterId = clusterId;
}
public String getNodeType() {
return nodeType;
}
public void setNodeType(String nodeType) {
this.nodeType = nodeType;
}
public long getAffinityGroupId() {
return affinityGroupId;
}
public void setAffinityGroupId(long affinityGroupId) {
this.affinityGroupId = affinityGroupId;
}
}

View File

@ -25,4 +25,5 @@ public class KubernetesClusterEventTypes {
public static final String EVENT_KUBERNETES_CLUSTER_UPGRADE = "KUBERNETES.CLUSTER.UPGRADE";
public static final String EVENT_KUBERNETES_CLUSTER_NODES_ADD = "KUBERNETES.CLUSTER.NODES.ADD";
public static final String EVENT_KUBERNETES_CLUSTER_NODES_REMOVE = "KUBERNETES.CLUSTER.NODES.REMOVE";
public static final String EVENT_KUBERNETES_CLUSTER_AFFINITY_UPDATE = "KUBERNETES.CLUSTER.AFFINITY.UPDATE";
}

View File

@ -49,6 +49,7 @@ import javax.naming.ConfigurationException;
import com.cloud.configuration.Resource;
import com.cloud.user.ResourceLimitService;
import org.apache.cloudstack.acl.ApiKeyPairVO;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.Role;
import org.apache.cloudstack.acl.RolePermissionEntity;
@ -57,7 +58,9 @@ import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.Rule;
import org.apache.cloudstack.acl.SecurityChecker;
import org.apache.cloudstack.affinity.AffinityGroupVO;
import org.apache.cloudstack.affinity.AffinityProcessorBase;
import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
import org.apache.cloudstack.annotation.AnnotationService;
import org.apache.cloudstack.annotation.dao.AnnotationDao;
import org.apache.cloudstack.api.ApiCommandResourceType;
@ -86,6 +89,7 @@ import org.apache.cloudstack.api.command.user.kubernetes.cluster.RemoveVirtualMa
import org.apache.cloudstack.api.command.user.kubernetes.cluster.ScaleKubernetesClusterCmd;
import org.apache.cloudstack.api.command.user.kubernetes.cluster.StartKubernetesClusterCmd;
import org.apache.cloudstack.api.command.user.kubernetes.cluster.StopKubernetesClusterCmd;
import org.apache.cloudstack.api.command.user.kubernetes.cluster.UpdateKubernetesClusterAffinityGroupCmd;
import org.apache.cloudstack.api.command.user.kubernetes.cluster.UpgradeKubernetesClusterCmd;
import org.apache.cloudstack.api.command.user.loadbalancer.AssignToLoadBalancerRuleCmd;
import org.apache.cloudstack.api.command.user.loadbalancer.CreateLoadBalancerRuleCmd;
@ -169,6 +173,7 @@ import com.cloud.kubernetes.cluster.actionworkers.KubernetesClusterScaleWorker;
import com.cloud.kubernetes.cluster.actionworkers.KubernetesClusterStartWorker;
import com.cloud.kubernetes.cluster.actionworkers.KubernetesClusterStopWorker;
import com.cloud.kubernetes.cluster.actionworkers.KubernetesClusterUpgradeWorker;
import com.cloud.kubernetes.cluster.dao.KubernetesClusterAffinityGroupMapDao;
import com.cloud.kubernetes.cluster.dao.KubernetesClusterDao;
import com.cloud.kubernetes.cluster.dao.KubernetesClusterDetailsDao;
import com.cloud.kubernetes.cluster.dao.KubernetesClusterVmMapDao;
@ -315,6 +320,8 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
@Inject
public KubernetesClusterDetailsDao kubernetesClusterDetailsDao;
@Inject
public KubernetesClusterAffinityGroupMapDao kubernetesClusterAffinityGroupMapDao;
@Inject
public KubernetesSupportedVersionDao kubernetesSupportedVersionDao;
@Inject
protected SSHKeyPairDao sshKeyPairDao;
@ -329,6 +336,8 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
@Inject
protected AffinityGroupDao affinityGroupDao;
@Inject
protected AffinityGroupVMMapDao affinityGroupVMMapDao;
@Inject
protected ServiceOfferingDao serviceOfferingDao;
@Inject
protected UserDataDao userDataDao;
@ -698,8 +707,8 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
} else if (Objects.nonNull(domainId)) {
dedicatedHosts = dedicatedResourceDao.listByDomainId(domainId);
}
for (DedicatedResourceVO dedicatedHost : dedicatedHosts) {
hosts.add(hostDao.findById(dedicatedHost.getHostId()));
for (DedicatedResourceVO dedicatedResource : dedicatedHosts) {
hosts.addAll(getHostsForDedicatedResource(dedicatedResource, zone));
useDedicatedHosts = true;
}
}
@ -767,6 +776,23 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
throw new InsufficientServerCapacityException(msg, DataCenter.class, zone.getId());
}
public List<HostVO> getHostsForDedicatedResource(DedicatedResourceVO dedicatedResource, DataCenter zone) {
if (dedicatedResource.getHostId() != null) {
HostVO host = hostDao.findById(dedicatedResource.getHostId());
return host != null ? List.of(host) : Collections.emptyList();
}
if (dedicatedResource.getClusterId() != null) {
return hostDao.findByClusterId(dedicatedResource.getClusterId());
}
if (dedicatedResource.getPodId() != null) {
return hostDao.findByPodId(dedicatedResource.getPodId(), Host.Type.Routing);
}
if (dedicatedResource.getDataCenterId() != null) {
return resourceManager.listAllHostsInOneZoneByType(Host.Type.Routing, dedicatedResource.getDataCenterId());
}
return Collections.emptyList();
}
protected void setNodeTypeServiceOfferingResponse(KubernetesClusterResponse response,
KubernetesClusterNodeType nodeType,
Long offeringId) {
@ -858,24 +884,38 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
List<KubernetesUserVmResponse> vmResponses = new ArrayList<>();
List<KubernetesClusterVmMapVO> vmList = kubernetesClusterVmMapDao.listByClusterId(kubernetesCluster.getId());
ResponseView respView = ResponseView.Restricted;
ResponseView userVmResponseView = ResponseView.Restricted;
Account caller = CallContext.current().getCallingAccount();
if (accountService.isRootAdmin(caller.getId())) {
respView = ResponseView.Full;
userVmResponseView = ResponseView.Full;
}
final String responseName = "virtualmachine";
if (vmList != null && !vmList.isEmpty()) {
for (KubernetesClusterVmMapVO vmMapVO : vmList) {
UserVmJoinVO userVM = userVmJoinDao.findById(vmMapVO.getVmId());
if (userVM != null) {
UserVmResponse vmResponse = ApiDBUtils.newUserVmResponse(respView, responseName, userVM,
EnumSet.of(VMDetails.nics), caller);
Map<Long, KubernetesClusterVmMapVO> vmMapById = vmList.stream()
.collect(Collectors.toMap(KubernetesClusterVmMapVO::getVmId, vm -> vm));
Long[] vmIds = vmMapById.keySet().toArray(new Long[0]);
List<UserVmJoinVO> userVmJoinVOs = userVmJoinDao.searchByIds(vmIds);
if (userVmJoinVOs != null && !userVmJoinVOs.isEmpty()) {
Map<Long, UserVmResponse> vmResponseMap = new HashMap<>();
for (UserVmJoinVO userVM : userVmJoinVOs) {
Long vmId = userVM.getId();
UserVmResponse vmResponse = vmResponseMap.get(vmId);
if (vmResponse == null) {
vmResponse = ApiDBUtils.newUserVmResponse(userVmResponseView, responseName, userVM,
EnumSet.of(VMDetails.nics, VMDetails.affgrp), caller);
vmResponseMap.put(vmId, vmResponse);
} else {
ApiDBUtils.fillVmDetails(userVmResponseView, vmResponse, userVM);
}
}
for (Map.Entry<Long, UserVmResponse> vmIdResponseEntry : vmResponseMap.entrySet()) {
KubernetesUserVmResponse kubernetesUserVmResponse = new KubernetesUserVmResponse();
try {
BeanUtils.copyProperties(kubernetesUserVmResponse, vmResponse);
BeanUtils.copyProperties(kubernetesUserVmResponse, vmIdResponseEntry.getValue());
} catch (IllegalAccessException | InvocationTargetException e) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to generate zone metrics response");
}
KubernetesClusterVmMapVO vmMapVO = vmMapById.get(vmIdResponseEntry.getKey());
kubernetesUserVmResponse.setExternalNode(vmMapVO.isExternalNode());
kubernetesUserVmResponse.setEtcdNode(vmMapVO.isEtcdNode());
kubernetesUserVmResponse.setNodeVersion(vmMapVO.getNodeVersion());
@ -905,10 +945,45 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
response.setClusterType(kubernetesCluster.getClusterType());
response.setCsiEnabled(kubernetesCluster.isCsiEnabled());
response.setCreated(kubernetesCluster.getCreated());
setNodeTypeAffinityGroupResponse(response, kubernetesCluster.getId());
return response;
}
protected void setNodeTypeAffinityGroupResponse(KubernetesClusterResponse response, long clusterId) {
setAffinityGroupResponseForNodeType(response, clusterId, CONTROL.name());
setAffinityGroupResponseForNodeType(response, clusterId, WORKER.name());
setAffinityGroupResponseForNodeType(response, clusterId, ETCD.name());
}
protected void setAffinityGroupResponseForNodeType(KubernetesClusterResponse response, long clusterId, String nodeType) {
List<Long> affinityGroupIds = kubernetesClusterAffinityGroupMapDao.listAffinityGroupIdsByClusterIdAndNodeType(clusterId, nodeType);
if (CollectionUtils.isEmpty(affinityGroupIds)) {
return;
}
List<String> affinityGroupUuids = new ArrayList<>();
List<String> affinityGroupNames = new ArrayList<>();
for (Long affinityGroupId : affinityGroupIds) {
AffinityGroupVO affinityGroup = affinityGroupDao.findById(affinityGroupId);
if (affinityGroup != null) {
affinityGroupUuids.add(affinityGroup.getUuid());
affinityGroupNames.add(affinityGroup.getName());
}
}
String affinityGroupUuidsCsv = String.join(",", affinityGroupUuids);
String affinityGroupNamesCsv = String.join(",", affinityGroupNames);
if (CONTROL.name().equals(nodeType)) {
response.setControlAffinityGroupIds(affinityGroupUuidsCsv);
response.setControlAffinityGroupNames(affinityGroupNamesCsv);
} else if (WORKER.name().equals(nodeType)) {
response.setWorkerAffinityGroupIds(affinityGroupUuidsCsv);
response.setWorkerAffinityGroupNames(affinityGroupNamesCsv);
} else if (ETCD.name().equals(nodeType)) {
response.setEtcdAffinityGroupIds(affinityGroupUuidsCsv);
response.setEtcdAffinityGroupNames(affinityGroupNamesCsv);
}
}
private DataCenter validateAndGetZoneForKubernetesCreateParameters(Long zoneId, Long networkId) {
DataCenter zone = dataCenterDao.findById(zoneId);
if (zone == null) {
@ -1190,6 +1265,20 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
return network;
}
private void persistAffinityGroupMappings(long clusterId, Map<String, List<Long>> affinityGroupNodeTypeMap) {
if (MapUtils.isEmpty(affinityGroupNodeTypeMap)) {
return;
}
for (Map.Entry<String, List<Long>> nodeTypeAffinityGroupEntry : affinityGroupNodeTypeMap.entrySet()) {
String nodeType = nodeTypeAffinityGroupEntry.getKey();
List<Long> affinityGroupIds = nodeTypeAffinityGroupEntry.getValue();
for (Long affinityGroupId : affinityGroupIds) {
kubernetesClusterAffinityGroupMapDao.persist(
new KubernetesClusterAffinityGroupMapVO(clusterId, nodeType, affinityGroupId));
}
}
}
private void addKubernetesClusterDetails(final KubernetesCluster kubernetesCluster, final Network network, final CreateKubernetesClusterCmd cmd) {
final String externalLoadBalancerIpAddress = cmd.getExternalLoadBalancerIpAddress();
final String dockerRegistryUserName = cmd.getDockerRegistryUserName();
@ -1629,6 +1718,7 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
}
Map<String, Long> templateNodeTypeMap = cmd.getTemplateNodeTypeMap();
Map<String, List<Long>> affinityGroupNodeTypeMap = cmd.getAffinityGroupNodeTypeMap();
final VMTemplateVO finalTemplate = getKubernetesServiceTemplate(zone, hypervisorType, templateNodeTypeMap, DEFAULT, clusterKubernetesVersion);
final VMTemplateVO controlNodeTemplate = getKubernetesServiceTemplate(zone, hypervisorType, templateNodeTypeMap, CONTROL, clusterKubernetesVersion);
final VMTemplateVO workerNodeTemplate = getKubernetesServiceTemplate(zone, hypervisorType, templateNodeTypeMap, WORKER, clusterKubernetesVersion);
@ -1674,6 +1764,7 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
}
newCluster.setCsiEnabled(cmd.getEnableCsi());
kubernetesClusterDao.persist(newCluster);
persistAffinityGroupMappings(newCluster.getId(), affinityGroupNodeTypeMap);
addKubernetesClusterDetails(newCluster, defaultNetwork, cmd);
return newCluster;
}
@ -1890,12 +1981,12 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
KUBEADMIN_ACCOUNT_NAME, "kubeadmin", null, UUID.randomUUID().toString(), User.Source.UNKNOWN));
keys = createUserApiKeyAndSecretKey(kube.getId());
} else {
String apiKey = kubeadmin.getApiKey();
String secretKey = kubeadmin.getSecretKey();
if (StringUtils.isAnyEmpty(apiKey, secretKey)) {
ApiKeyPairVO latestKeypair = ApiDBUtils.searchForLatestUserKeyPair(kubeadmin.getId());
if (latestKeypair == null) {
keys = createUserApiKeyAndSecretKey(kubeadmin.getId());
} else {
keys = new String[]{apiKey, secretKey};
keys = new String[]{latestKeypair.getApiKey(), latestKeypair.getSecretKey()};
}
}
return keys;
@ -2230,6 +2321,94 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
return upgradeWorker.upgradeCluster();
}
@Override
@ActionEvent(eventType = KubernetesClusterEventTypes.EVENT_KUBERNETES_CLUSTER_AFFINITY_UPDATE,
eventDescription = "updating Kubernetes cluster affinity groups")
public boolean updateKubernetesClusterAffinityGroups(UpdateKubernetesClusterAffinityGroupCmd cmd) throws CloudRuntimeException {
if (!KubernetesServiceEnabled.value()) {
logAndThrow(Level.ERROR, "Kubernetes Service plugin is disabled");
}
KubernetesClusterVO kubernetesCluster = validateClusterForAffinityGroupUpdate(cmd.getId());
Map<String, List<Long>> affinityGroupNodeTypeMap = cmd.getAffinityGroupNodeTypeMap();
validateNodeAffinityGroups(affinityGroupNodeTypeMap, kubernetesCluster.getAccountId());
final Long clusterId = kubernetesCluster.getId();
Transaction.execute(new TransactionCallbackNoReturn() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
kubernetesClusterAffinityGroupMapDao.removeByClusterId(clusterId);
persistAffinityGroupMappings(clusterId, affinityGroupNodeTypeMap);
syncVmAffinityGroups(clusterId, affinityGroupNodeTypeMap);
}
});
logger.info("Updated affinity groups for Kubernetes cluster {}", kubernetesCluster.getName());
return true;
}
private KubernetesClusterVO validateClusterForAffinityGroupUpdate(Long clusterId) {
KubernetesClusterVO kubernetesCluster = kubernetesClusterDao.findById(clusterId);
if (Objects.isNull(kubernetesCluster) || Objects.nonNull(kubernetesCluster.getRemoved())) {
throw new InvalidParameterValueException("Invalid Kubernetes cluster ID");
}
if (!KubernetesCluster.ClusterType.CloudManaged.equals(kubernetesCluster.getClusterType())) {
throw new InvalidParameterValueException("Affinity groups can only be updated for CloudManaged Kubernetes clusters");
}
if (!KubernetesCluster.State.Stopped.equals(kubernetesCluster.getState())) {
throw new InvalidParameterValueException(String.format(
"Kubernetes cluster %s must be stopped before updating affinity groups (current state: %s)",
kubernetesCluster.getName(), kubernetesCluster.getState()));
}
accountManager.checkAccess(CallContext.current().getCallingAccount(),
SecurityChecker.AccessType.OperateEntry, false, kubernetesCluster);
return kubernetesCluster;
}
private void validateNodeAffinityGroups(Map<String, List<Long>> affinityGroupNodeTypeMap, long ownerAccountId) {
if (MapUtils.isEmpty(affinityGroupNodeTypeMap)) {
return;
}
Account owner = accountDao.findById(ownerAccountId);
for (List<Long> affinityGroupIds : affinityGroupNodeTypeMap.values()) {
for (Long affinityGroupId : affinityGroupIds) {
AffinityGroupVO affinityGroup = affinityGroupDao.findById(affinityGroupId);
if (Objects.isNull(affinityGroup)) {
throw new InvalidParameterValueException("Unable to find affinity group with ID: " + affinityGroupId);
}
if (affinityGroup.getAccountId() != owner.getAccountId()) {
throw new InvalidParameterValueException(String.format(
"Affinity group %s does not belong to the cluster owner account %s",
affinityGroup.getName(), owner.getAccountName()));
}
}
}
}
private void syncVmAffinityGroups(Long clusterId, Map<String, List<Long>> affinityGroupNodeTypeMap) {
List<KubernetesClusterVmMapVO> clusterVmMappings = kubernetesClusterVmMapDao.listByClusterId(clusterId);
if (CollectionUtils.isEmpty(clusterVmMappings)) {
return;
}
Map<String, List<Long>> nodeTypeAffinityMap = MapUtils.isEmpty(affinityGroupNodeTypeMap)
? Collections.emptyMap() : affinityGroupNodeTypeMap;
for (KubernetesClusterVmMapVO clusterVmMapping : clusterVmMappings) {
if (clusterVmMapping.isExternalNode()) {
continue;
}
String nodeType = getNodeType(clusterVmMapping);
affinityGroupVMMapDao.updateMap(clusterVmMapping.getVmId(),
nodeTypeAffinityMap.getOrDefault(nodeType, Collections.emptyList()));
}
}
private String getNodeType(KubernetesClusterVmMapVO clusterVmMapping) {
if (clusterVmMapping.isControlNode()) {
return CONTROL.name();
} else if (clusterVmMapping.isEtcdNode()) {
return ETCD.name();
}
return WORKER.name();
}
private void updateNodeCount(KubernetesClusterVO kubernetesCluster) {
List<KubernetesClusterVmMapVO> nodeList = kubernetesClusterVmMapDao.listByClusterId(kubernetesCluster.getId());
kubernetesCluster.setControlNodeCount(nodeList.stream().filter(KubernetesClusterVmMapVO::isControlNode).count());
@ -2291,6 +2470,7 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
if (validNodeIds.isEmpty()) {
throw new CloudRuntimeException("No valid nodes found to be added to the Kubernetes cluster");
}
validateNodeAffinityGroups(validNodeIds, kubernetesCluster);
KubernetesClusterAddWorker addWorker = new KubernetesClusterAddWorker(kubernetesCluster, KubernetesClusterManagerImpl.this);
addWorker = ComponentContext.inject(addWorker);
return addWorker.addNodesToCluster(validNodeIds, cmd.isMountCksIsoOnVr(), cmd.isManualUpgrade());
@ -2350,6 +2530,98 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
return validNodeIds;
}
protected void validateNodeAffinityGroups(List<Long> nodeIds, KubernetesCluster cluster) {
List<Long> workerAffinityGroupIds = kubernetesClusterAffinityGroupMapDao.listAffinityGroupIdsByClusterIdAndNodeType(
cluster.getId(), WORKER.name());
if (CollectionUtils.isEmpty(workerAffinityGroupIds)) {
return;
}
Set<Long> existingWorkerHostIds = getExistingWorkerHostIds(cluster);
for (Long affinityGroupId : workerAffinityGroupIds) {
AffinityGroupVO affinityGroup = affinityGroupDao.findById(affinityGroupId);
if (affinityGroup == null) {
continue;
}
validateNodesAgainstExistingWorkers(nodeIds, existingWorkerHostIds, affinityGroup, cluster);
validateNewNodesAntiAffinity(nodeIds, affinityGroup, cluster);
}
}
protected Set<Long> getExistingWorkerHostIds(KubernetesCluster cluster) {
List<KubernetesClusterVmMapVO> existingWorkerVms = kubernetesClusterVmMapDao.listByClusterIdAndVmType(cluster.getId(), WORKER);
Set<Long> existingWorkerHostIds = new HashSet<>();
for (KubernetesClusterVmMapVO workerVmMap : existingWorkerVms) {
VMInstanceVO workerVm = vmInstanceDao.findById(workerVmMap.getVmId());
if (workerVm != null && workerVm.getHostId() != null) {
existingWorkerHostIds.add(workerVm.getHostId());
}
}
return existingWorkerHostIds;
}
protected void validateNodesAgainstExistingWorkers(List<Long> nodeIds, Set<Long> existingWorkerHostIds,
AffinityGroupVO affinityGroup, KubernetesCluster cluster) {
for (Long nodeId : nodeIds) {
VMInstanceVO node = vmInstanceDao.findById(nodeId);
if (node == null || node.getHostId() == null) {
continue;
}
Long nodeHostId = node.getHostId();
String nodeHostName = getHostName(nodeHostId);
if (AffinityProcessorBase.AFFINITY_TYPE_HOST_ANTI.equals(affinityGroup.getType())) {
if (existingWorkerHostIds.contains(nodeHostId)) {
throw new InvalidParameterValueException(String.format(
"Cannot add VM %s to cluster %s. VM is running on host %s which violates the cluster's " +
"host anti-affinity rule (affinity group: %s). Existing worker VMs are already running on this host.",
node.getInstanceName(), cluster.getName(), nodeHostName, affinityGroup.getName()));
}
} else if (AffinityProcessorBase.AFFINITY_TYPE_HOST.equals(affinityGroup.getType())) {
if (!existingWorkerHostIds.isEmpty() && !existingWorkerHostIds.contains(nodeHostId)) {
List<String> existingHostNames = existingWorkerHostIds.stream()
.map(this::getHostName)
.collect(Collectors.toList());
throw new InvalidParameterValueException(String.format(
"Cannot add VM %s to cluster %s. VM is running on host %s which violates the cluster's " +
"host affinity rule (affinity group: %s). All worker VMs must run on the same host. " +
"Existing workers are on host(s): %s.",
node.getInstanceName(), cluster.getName(), nodeHostName, affinityGroup.getName(),
String.join(", ", existingHostNames)));
}
}
}
}
protected void validateNewNodesAntiAffinity(List<Long> nodeIds, AffinityGroupVO affinityGroup, KubernetesCluster cluster) {
if (!AffinityProcessorBase.AFFINITY_TYPE_HOST_ANTI.equals(affinityGroup.getType())) {
return;
}
Set<Long> newNodeHostIds = new HashSet<>();
for (Long nodeId : nodeIds) {
VMInstanceVO node = vmInstanceDao.findById(nodeId);
if (node != null && node.getHostId() != null) {
Long nodeHostId = node.getHostId();
if (newNodeHostIds.contains(nodeHostId)) {
String nodeHostName = getHostName(nodeHostId);
throw new InvalidParameterValueException(String.format(
"Cannot add VM %s to cluster %s. Multiple VMs being added are running on the same host %s, " +
"which violates the cluster's host anti-affinity rule (affinity group: %s).",
node.getInstanceName(), cluster.getName(), nodeHostName, affinityGroup.getName()));
}
newNodeHostIds.add(nodeHostId);
}
}
}
protected String getHostName(Long hostId) {
HostVO host = hostDao.findById(hostId);
return host != null ? host.getName() : String.valueOf(hostId);
}
@Override
public List<RemoveVirtualMachinesFromKubernetesClusterResponse> removeVmsFromCluster(RemoveVirtualMachinesFromKubernetesClusterCmd cmd) {
if (!KubernetesServiceEnabled.value()) {
@ -2486,6 +2758,7 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
cmdList.add(RemoveVirtualMachinesFromKubernetesClusterCmd.class);
cmdList.add(AddNodesToKubernetesClusterCmd.class);
cmdList.add(RemoveNodesFromKubernetesClusterCmd.class);
cmdList.add(UpdateKubernetesClusterAffinityGroupCmd.class);
return cmdList;
}

View File

@ -32,6 +32,7 @@ import org.apache.cloudstack.api.command.user.kubernetes.cluster.RemoveVirtualMa
import org.apache.cloudstack.api.command.user.kubernetes.cluster.ScaleKubernetesClusterCmd;
import org.apache.cloudstack.api.command.user.kubernetes.cluster.StartKubernetesClusterCmd;
import org.apache.cloudstack.api.command.user.kubernetes.cluster.StopKubernetesClusterCmd;
import org.apache.cloudstack.api.command.user.kubernetes.cluster.UpdateKubernetesClusterAffinityGroupCmd;
import org.apache.cloudstack.api.command.user.kubernetes.cluster.UpgradeKubernetesClusterCmd;
import org.apache.cloudstack.api.response.KubernetesClusterConfigResponse;
import org.apache.cloudstack.api.response.KubernetesClusterResponse;
@ -171,6 +172,8 @@ public interface KubernetesClusterService extends PluggableService, Configurable
boolean upgradeKubernetesCluster(UpgradeKubernetesClusterCmd cmd) throws CloudRuntimeException;
boolean updateKubernetesClusterAffinityGroups(UpdateKubernetesClusterAffinityGroupCmd cmd) throws CloudRuntimeException;
boolean addVmsToCluster(AddVirtualMachinesToKubernetesClusterCmd cmd);
boolean addNodesToKubernetesCluster(AddNodesToKubernetesClusterCmd cmd);

View File

@ -18,12 +18,16 @@ package com.cloud.kubernetes.cluster;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.inject.Inject;
import org.apache.cloudstack.affinity.AffinityGroup;
import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.offering.ServiceOffering;
import com.cloud.service.dao.ServiceOfferingDao;
@ -66,6 +70,8 @@ public class KubernetesServiceHelperImpl extends AdapterBase implements Kubernet
@Inject
protected VMTemplateDao vmTemplateDao;
@Inject
protected AffinityGroupDao affinityGroupDao;
@Inject
KubernetesClusterService kubernetesClusterService;
protected void setEventTypeEntityDetails(Class<?> eventTypeDefinedClass, Class<?> entityClass) {
@ -123,6 +129,27 @@ public class KubernetesServiceHelperImpl extends AdapterBase implements Kubernet
throw new CloudRuntimeException(msg);
}
@Override
public void checkVmAffinityGroupsCanBeUpdated(UserVm userVm) {
if (!UserVmManager.CKS_NODE.equals(userVm.getUserVmType())) {
return;
}
KubernetesClusterVmMapVO clusterVmMapping = kubernetesClusterVmMapDao.findByVmId(userVm.getId());
if (Objects.isNull(clusterVmMapping)) {
return;
}
KubernetesCluster kubernetesCluster = kubernetesClusterDao.findById(clusterVmMapping.getClusterId());
String errorMessage = "Affinity groups cannot be updated for a VM part of Kubernetes cluster";
if (Objects.nonNull(kubernetesCluster)) {
if (KubernetesCluster.ClusterType.ExternalManaged.equals(kubernetesCluster.getClusterType())) {
return;
}
errorMessage += String.format(": %s", kubernetesCluster.getName());
}
errorMessage += ". Please use the cluster's Change Affinity option instead.";
throw new CloudRuntimeException(errorMessage);
}
@Override
public boolean isValidNodeType(String nodeType) {
if (StringUtils.isBlank(nodeType)) {
@ -244,6 +271,81 @@ public class KubernetesServiceHelperImpl extends AdapterBase implements Kubernet
return mapping;
}
protected void checkNodeTypeAffinityGroupEntryCompleteness(String nodeType, String affinityGroupUuids) {
if (StringUtils.isAnyBlank(nodeType, affinityGroupUuids)) {
String error = String.format("Any Node Type to Affinity Group entry should have valid '%s' and '%s' values",
VmDetailConstants.CKS_NODE_TYPE, VmDetailConstants.AFFINITY_GROUP);
logger.error(error);
throw new InvalidParameterValueException(error);
}
}
protected void checkNodeTypeAffinityGroupEntryNodeType(String nodeType) {
if (!isValidNodeType(nodeType)) {
String error = String.format("The provided value '%s' for Node Type is invalid", nodeType);
logger.error(error);
throw new InvalidParameterValueException(error);
}
}
protected Long validateAffinityGroupUuidAndGetId(String affinityGroupUuid) {
if (StringUtils.isBlank(affinityGroupUuid)) {
String error = "Empty affinity group UUID provided";
logger.error(error);
throw new InvalidParameterValueException(error);
}
AffinityGroup affinityGroup = affinityGroupDao.findByUuid(affinityGroupUuid);
if (affinityGroup == null) {
String error = String.format("Cannot find an affinity group with ID %s", affinityGroupUuid);
logger.error(error);
throw new InvalidParameterValueException(error);
}
return affinityGroup.getId();
}
protected List<Long> validateAndGetAffinityGroupIds(String affinityGroupUuids) {
String[] uuids = affinityGroupUuids.split(",");
List<Long> affinityGroupIds = new ArrayList<>();
for (String uuid : uuids) {
String trimmedUuid = uuid.trim();
Long affinityGroupId = validateAffinityGroupUuidAndGetId(trimmedUuid);
affinityGroupIds.add(affinityGroupId);
}
return affinityGroupIds;
}
protected void addNodeTypeAffinityGroupEntry(String nodeType, List<Long> affinityGroupIds, Map<String, List<Long>> nodeTypeToAffinityGroupIds) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Node Type: '%s' should use affinity group IDs: '%s'", nodeType, affinityGroupIds));
}
KubernetesClusterNodeType clusterNodeType = KubernetesClusterNodeType.valueOf(nodeType.toUpperCase());
nodeTypeToAffinityGroupIds.put(clusterNodeType.name(), affinityGroupIds);
}
protected void processNodeTypeAffinityGroupEntryAndAddToMappingIfValid(Map<String, String> nodeTypeAffinityConfig, Map<String, List<Long>> nodeTypeToAffinityGroupIds) {
if (MapUtils.isEmpty(nodeTypeAffinityConfig)) {
return;
}
String nodeType = nodeTypeAffinityConfig.get(VmDetailConstants.CKS_NODE_TYPE);
String affinityGroupUuids = nodeTypeAffinityConfig.get(VmDetailConstants.AFFINITY_GROUP);
checkNodeTypeAffinityGroupEntryCompleteness(nodeType, affinityGroupUuids);
checkNodeTypeAffinityGroupEntryNodeType(nodeType);
List<Long> affinityGroupIds = validateAndGetAffinityGroupIds(affinityGroupUuids);
addNodeTypeAffinityGroupEntry(nodeType, affinityGroupIds, nodeTypeToAffinityGroupIds);
}
@Override
public Map<String, List<Long>> getAffinityGroupNodeTypeMap(Map<String, Map<String, String>> affinityGroupNodeTypeMap) {
Map<String, List<Long>> nodeTypeToAffinityGroupIds = new HashMap<>();
if (MapUtils.isNotEmpty(affinityGroupNodeTypeMap)) {
for (Map<String, String> nodeTypeAffinityConfig : affinityGroupNodeTypeMap.values()) {
processNodeTypeAffinityGroupEntryAndAddToMappingIfValid(nodeTypeAffinityConfig, nodeTypeToAffinityGroupIds);
}
}
return nodeTypeToAffinityGroupIds;
}
public void cleanupForAccount(Account account) {
kubernetesClusterService.cleanupForAccount(account);
}

View File

@ -28,9 +28,11 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@ -63,7 +65,9 @@ import com.cloud.vm.VirtualMachine;
import com.cloud.vm.dao.NicDao;
import com.cloud.vm.UserVmManager;
import org.apache.cloudstack.affinity.AffinityGroupVO;
import org.apache.cloudstack.affinity.AffinityProcessorBase;
import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.command.user.firewall.CreateFirewallRuleCmd;
@ -90,6 +94,7 @@ import com.cloud.kubernetes.cluster.KubernetesClusterDetailsVO;
import com.cloud.kubernetes.cluster.KubernetesClusterManagerImpl;
import com.cloud.kubernetes.cluster.KubernetesClusterVO;
import com.cloud.kubernetes.cluster.KubernetesClusterVmMapVO;
import com.cloud.kubernetes.cluster.dao.KubernetesClusterAffinityGroupMapDao;
import com.cloud.kubernetes.cluster.dao.KubernetesClusterDao;
import com.cloud.kubernetes.cluster.dao.KubernetesClusterDetailsDao;
import com.cloud.kubernetes.cluster.dao.KubernetesClusterVmMapDao;
@ -124,10 +129,12 @@ import com.cloud.utils.fsm.NoTransitionException;
import com.cloud.utils.fsm.StateMachine2;
import com.cloud.utils.ssh.SshHelper;
import com.cloud.vm.VMInstanceDetailVO;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.UserVmService;
import com.cloud.vm.UserVmVO;
import com.cloud.vm.VmDetailConstants;
import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.dao.VMInstanceDao;
import com.cloud.vm.dao.VMInstanceDetailsDao;
import static com.cloud.kubernetes.cluster.KubernetesServiceHelper.KubernetesClusterNodeType.CONTROL;
@ -213,10 +220,15 @@ public class KubernetesClusterActionWorker {
private NicDao nicDao;
@Inject
protected AffinityGroupDao affinityGroupDao;
@Inject
protected AffinityGroupVMMapDao affinityGroupVMMapDao;
@Inject
protected VMInstanceDao vmInstanceDao;
protected KubernetesClusterDao kubernetesClusterDao;
protected KubernetesClusterVmMapDao kubernetesClusterVmMapDao;
protected KubernetesClusterDetailsDao kubernetesClusterDetailsDao;
protected KubernetesClusterAffinityGroupMapDao kubernetesClusterAffinityGroupMapDao;
protected KubernetesSupportedVersionDao kubernetesSupportedVersionDao;
protected KubernetesCluster kubernetesCluster;
@ -251,6 +263,7 @@ public class KubernetesClusterActionWorker {
this.kubernetesClusterDao = clusterManager.kubernetesClusterDao;
this.kubernetesClusterDetailsDao = clusterManager.kubernetesClusterDetailsDao;
this.kubernetesClusterVmMapDao = clusterManager.kubernetesClusterVmMapDao;
this.kubernetesClusterAffinityGroupMapDao = clusterManager.kubernetesClusterAffinityGroupMapDao;
this.kubernetesSupportedVersionDao = clusterManager.kubernetesSupportedVersionDao;
this.manager = clusterManager;
}
@ -1112,4 +1125,76 @@ public class KubernetesClusterActionWorker {
}
return null;
}
protected List<Long> getAffinityGroupIdsForNodeType(KubernetesClusterNodeType nodeType) {
List<Long> affinityGroupIds = kubernetesClusterAffinityGroupMapDao.listAffinityGroupIdsByClusterIdAndNodeType(
kubernetesCluster.getId(), nodeType.name());
if (CollectionUtils.isEmpty(affinityGroupIds)) {
return new ArrayList<>();
}
return new ArrayList<>(affinityGroupIds);
}
protected List<Long> getMergedAffinityGroupIds(KubernetesClusterNodeType nodeType, Long domainId, Long accountId) {
List<Long> affinityGroupIds = getAffinityGroupIdsForNodeType(nodeType);
Long explicitAffinityGroupId = getExplicitAffinityGroup(domainId, accountId);
if (explicitAffinityGroupId != null && !affinityGroupIds.contains(explicitAffinityGroupId)) {
affinityGroupIds.add(explicitAffinityGroupId);
}
return affinityGroupIds.isEmpty() ? null : affinityGroupIds;
}
private Set<Long> getRunningVmHostIds(Long affinityGroupId) {
return affinityGroupVMMapDao.listVmIdsByAffinityGroup(affinityGroupId).stream()
.map(vmInstanceDao::findById)
.filter(vm -> Objects.nonNull(vm) && Objects.nonNull(vm.getHostId()) && VirtualMachine.State.Running.equals(vm.getState()))
.map(VMInstanceVO::getHostId)
.collect(Collectors.toSet());
}
protected AffinityConstraints resolveAffinityConstraints(KubernetesClusterNodeType nodeType, Long domainId, Long accountId) {
Set<Long> antiAffinityOccupiedHosts = new HashSet<>();
Long requiredHostId = null;
boolean hasHostAntiAffinity = false;
boolean hasHostAffinity = false;
if (Objects.nonNull(nodeType)) {
List<Long> affinityGroupIds = getMergedAffinityGroupIds(nodeType, domainId, accountId);
if (CollectionUtils.isNotEmpty(affinityGroupIds)) {
for (Long affinityGroupId : affinityGroupIds) {
AffinityGroupVO affinityGroup = affinityGroupDao.findById(affinityGroupId);
if (Objects.isNull(affinityGroup)) {
continue;
}
if (AffinityProcessorBase.AFFINITY_TYPE_HOST_ANTI.equals(affinityGroup.getType())) {
hasHostAntiAffinity = true;
antiAffinityOccupiedHosts.addAll(getRunningVmHostIds(affinityGroupId));
} else if (AffinityProcessorBase.AFFINITY_TYPE_HOST.equals(affinityGroup.getType())) {
hasHostAffinity = true;
Set<Long> hostIds = getRunningVmHostIds(affinityGroupId);
if (CollectionUtils.isNotEmpty(hostIds)) {
requiredHostId = hostIds.iterator().next();
}
}
}
}
}
return new AffinityConstraints(hasHostAntiAffinity, hasHostAffinity, antiAffinityOccupiedHosts, requiredHostId);
}
protected static class AffinityConstraints {
final boolean hasHostAntiAffinity;
final boolean hasHostAffinity;
final Set<Long> antiAffinityOccupiedHosts;
final Long requiredHostId;
AffinityConstraints(boolean hasHostAntiAffinity, boolean hasHostAffinity,
Set<Long> antiAffinityOccupiedHosts, Long requiredHostId) {
this.hasHostAntiAffinity = hasHostAntiAffinity;
this.hasHostAffinity = hasHostAffinity;
this.antiAffinityOccupiedHosts = antiAffinityOccupiedHosts;
this.requiredHostId = requiredHostId;
}
}
}

View File

@ -348,6 +348,7 @@ public class KubernetesClusterDestroyWorker extends KubernetesClusterResourceMod
stateTransitTo(kubernetesCluster.getId(), KubernetesCluster.Event.OperationSucceeded);
annotationDao.removeByEntityType(AnnotationService.EntityType.KUBERNETES_CLUSTER.name(), kubernetesCluster.getUuid());
kubernetesClusterDetailsDao.removeDetails(kubernetesCluster.getId());
kubernetesClusterAffinityGroupMapDao.removeByClusterId(kubernetesCluster.getId());
boolean deleted = kubernetesClusterDao.remove(kubernetesCluster.getId());
if (!deleted) {
logMessage(Level.WARN, String.format("Failed to delete Kubernetes cluster: %s", kubernetesCluster), null);

View File

@ -26,7 +26,6 @@ import static com.cloud.utils.db.Transaction.execute;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -116,7 +115,6 @@ import com.cloud.vm.Nic;
import com.cloud.vm.UserVmManager;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VmDetailConstants;
import com.cloud.vm.dao.VMInstanceDao;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.context.CallContext;
import org.apache.logging.log4j.Level;
@ -152,8 +150,6 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
@Inject
protected LoadBalancerDao loadBalancerDao;
@Inject
protected VMInstanceDao vmInstanceDao;
@Inject
protected UserVmManager userVmManager;
@Inject
protected LaunchPermissionDao launchPermissionDao;
@ -177,8 +173,33 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
kubernetesClusterNodeNamePrefix = getKubernetesClusterNodeNamePrefix();
}
protected List<HostVO> filterHostsByAffinityConstraints(List<HostVO> hosts, AffinityConstraints constraints, DataCenter zone)
throws InsufficientServerCapacityException {
if (constraints.hasHostAffinity && Objects.nonNull(constraints.requiredHostId)) {
hosts = hosts.stream().filter(host -> host.getId() == constraints.requiredHostId.longValue()).collect(Collectors.toList());
if (CollectionUtils.isEmpty(hosts)) {
String msg = String.format("Cannot find capacity for Kubernetes cluster: host affinity requires all VMs on host %d but it is not available in zone %s",
constraints.requiredHostId, zone.getName());
throw new InsufficientServerCapacityException(msg, DataCenter.class, zone.getId());
}
}
if (constraints.hasHostAntiAffinity) {
hosts = hosts.stream().filter(host -> !constraints.antiAffinityOccupiedHosts.contains(host.getId())).collect(Collectors.toList());
if (CollectionUtils.isEmpty(hosts)) {
String msg = String.format("Cannot find capacity for Kubernetes cluster: host anti-affinity requires each VM on a separate host, " +
"but all %d available hosts in zone %s are already occupied by existing cluster VMs",
constraints.antiAffinityOccupiedHosts.size(), zone.getName());
throw new InsufficientServerCapacityException(msg, DataCenter.class, zone.getId());
}
}
return hosts;
}
protected DeployDestination plan(final long nodesCount, final DataCenter zone, final ServiceOffering offering,
final Long domainId, final Long accountId, final Hypervisor.HypervisorType hypervisorType, CPU.CPUArch arch) throws InsufficientServerCapacityException {
final Long domainId, final Long accountId, final Hypervisor.HypervisorType hypervisorType,
CPU.CPUArch arch, KubernetesClusterNodeType nodeType) throws InsufficientServerCapacityException {
final int cpu_requested = offering.getCpu() * offering.getSpeed();
final long ram_requested = offering.getRamSize() * 1024L * 1024L;
boolean useDedicatedHosts = false;
@ -191,26 +212,30 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
} else if (Objects.nonNull(domainId)) {
dedicatedHosts = dedicatedResourceDao.listByDomainId(domainId);
}
for (DedicatedResourceVO dedicatedHost : dedicatedHosts) {
hosts.add(hostDao.findById(dedicatedHost.getHostId()));
for (DedicatedResourceVO dedicatedResource : dedicatedHosts) {
hosts.addAll(manager.getHostsForDedicatedResource(dedicatedResource, zone));
useDedicatedHosts = true;
}
}
if (hosts.isEmpty()) {
hosts = resourceManager.listAllHostsInOneZoneByType(Host.Type.Routing, zone.getId());
}
if (hypervisorType != null) {
if (Objects.nonNull(hypervisorType)) {
hosts = hosts.stream().filter(x -> x.getHypervisorType() == hypervisorType).collect(Collectors.toList());
}
if (arch != null) {
if (Objects.nonNull(arch)) {
hosts = hosts.stream().filter(x -> x.getArch().equals(arch)).collect(Collectors.toList());
}
if (CollectionUtils.isEmpty(hosts)) {
String msg = String.format("Cannot find enough capacity for Kubernetes cluster(requested cpu=%d memory=%s) with offering: %s hypervisor: %s and arch: %s",
cpu_requested * nodesCount, toHumanReadableSize(ram_requested * nodesCount), offering.getName(), clusterTemplate.getHypervisorType().toString(), arch.getType());
cpu_requested * nodesCount, toHumanReadableSize(ram_requested * nodesCount), offering.getName(), clusterTemplate.getHypervisorType().toString(),
Objects.nonNull(arch) ? arch.getType() : "null");
logAndThrow(Level.WARN, msg, new InsufficientServerCapacityException(msg, DataCenter.class, zone.getId()));
}
AffinityConstraints affinityConstraints = resolveAffinityConstraints(nodeType, domainId, accountId);
hosts = filterHostsByAffinityConstraints(hosts, affinityConstraints, zone);
final Map<String, Pair<HostVO, Integer>> hosts_with_resevered_capacity = new ConcurrentHashMap<String, Pair<HostVO, Integer>>();
for (HostVO h : hosts) {
hosts_with_resevered_capacity.put(h.getUuid(), new Pair<HostVO, Integer>(h, 0));
@ -230,6 +255,9 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
continue;
}
int reserved = hp.second();
if (affinityConstraints.hasHostAntiAffinity && reserved > 0) {
continue;
}
reserved++;
ClusterVO cluster = clusterDao.findById(h.getClusterId());
ClusterDetailsVO cluster_detail_cpu = clusterDetailsDao.findDetail(cluster.getId(), "cpuOvercommitRatio");
@ -264,10 +292,17 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
}
return new DeployDestination(zone, null, null, null);
}
String msg = String.format("Cannot find enough capacity for Kubernetes cluster(requested cpu=%d memory=%s) with offering: %s hypervisor: %s and arch: %s",
cpu_requested * nodesCount, toHumanReadableSize(ram_requested * nodesCount), offering.getName(), clusterTemplate.getHypervisorType().toString(), arch.getType());
logger.warn(msg);
String msg;
if (affinityConstraints.hasHostAntiAffinity) {
msg = String.format("Cannot find enough capacity for Kubernetes cluster (requested cpu=%d memory=%s) with offering: %s. " +
"Host anti-affinity requires %d separate hosts but not enough suitable hosts are available in zone %s",
cpu_requested * nodesCount, toHumanReadableSize(ram_requested * nodesCount), offering.getName(),
nodesCount, zone.getName());
} else {
msg = String.format("Cannot find enough capacity for Kubernetes cluster(requested cpu=%d memory=%s) with offering: %s hypervisor: %s and arch: %s",
cpu_requested * nodesCount, toHumanReadableSize(ram_requested * nodesCount), offering.getName(), clusterTemplate.getHypervisorType().toString(),
Objects.nonNull(arch) ? arch.getType() : "null");
}
throw new InsufficientServerCapacityException(msg, DataCenter.class, zone.getId());
}
@ -296,7 +331,7 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
if (logger.isDebugEnabled()) {
logger.debug("Checking deployment destination for {} nodes on Kubernetes cluster : {} in zone : {}", nodeType.name(), kubernetesCluster.getName(), zone.getName());
}
DeployDestination planForNodeType = plan(nodes, zone, nodeOffering, domainId, accountId, hypervisorType, arch);
DeployDestination planForNodeType = plan(nodes, zone, nodeOffering, domainId, accountId, hypervisorType, arch, nodeType);
destinationMap.put(nodeType.name(), planForNodeType);
}
return destinationMap;
@ -426,21 +461,19 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
if (StringUtils.isNotBlank(kubernetesCluster.getKeyPair())) {
keypairs.add(kubernetesCluster.getKeyPair());
}
Long affinityGroupId = getExplicitAffinityGroup(domainId, accountId);
List<Long> affinityGroupIds = getMergedAffinityGroupIds(WORKER, domainId, accountId);
if (kubernetesCluster.getSecurityGroupId() != null && networkModel.checkSecurityGroupSupportForNetwork(owner, zone, networkIds, List.of(kubernetesCluster.getSecurityGroupId()))) {
List<Long> securityGroupIds = new ArrayList<>();
securityGroupIds.add(kubernetesCluster.getSecurityGroupId());
nodeVm = userVmService.createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, workerNodeTemplate, networkIds, securityGroupIds, owner,
hostName, hostName, null, null, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST,base64UserData, null, null, keypairs,
null, addrs, null, null, Objects.nonNull(affinityGroupId) ?
Collections.singletonList(affinityGroupId) : null, customParameterMap, null, null, null,
null, addrs, null, null, affinityGroupIds, customParameterMap, null, null, null,
null, true, null, UserVmManager.CKS_NODE, null, null);
} else {
nodeVm = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, workerNodeTemplate, networkIds, owner,
hostName, hostName, null, null, null, null,
Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, null, null, keypairs,
null, addrs, null, null, Objects.nonNull(affinityGroupId) ?
Collections.singletonList(affinityGroupId) : null, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE, null, null, null);
null, addrs, null, null, affinityGroupIds, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE, null, null, null);
}
if (logger.isInfoEnabled()) {
logger.info("Created node VM : {}, {} in the Kubernetes cluster : {}", hostName, nodeVm, kubernetesCluster.getName());

View File

@ -24,10 +24,10 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.inject.Inject;
import com.cloud.kubernetes.cluster.KubernetesServiceHelper.KubernetesClusterNodeType;
import com.cloud.service.ServiceOfferingVO;
import com.cloud.storage.VMTemplateVO;
@ -63,7 +63,6 @@ import com.cloud.utils.ssh.SshHelper;
import com.cloud.vm.UserVmVO;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.dao.VMInstanceDao;
import org.apache.logging.log4j.Level;
import static com.cloud.kubernetes.cluster.KubernetesServiceHelper.KubernetesClusterNodeType.CONTROL;
@ -73,9 +72,6 @@ import static com.cloud.kubernetes.cluster.KubernetesServiceHelper.KubernetesClu
public class KubernetesClusterScaleWorker extends KubernetesClusterResourceModifierActionWorker {
@Inject
protected VMInstanceDao vmInstanceDao;
private Map<String, ServiceOffering> serviceOfferingNodeTypeMap;
private Long clusterSize;
private List<Long> nodeIds;
@ -325,7 +321,7 @@ public class KubernetesClusterScaleWorker extends KubernetesClusterResourceModif
}
}
private void validateKubernetesClusterScaleSizeParameters() throws CloudRuntimeException {
private void validateKubernetesClusterScaleSizeParameters(KubernetesClusterNodeType nodeType) throws CloudRuntimeException {
final long originalClusterSize = kubernetesCluster.getNodeCount();
if (network == null) {
logTransitStateToFailedIfNeededAndThrow(Level.WARN, String.format("Scaling failed for Kubernetes cluster : %s, cluster network not found", kubernetesCluster.getName()));
@ -341,12 +337,12 @@ public class KubernetesClusterScaleWorker extends KubernetesClusterResourceModif
VMTemplateVO clusterTemplate = templateDao.findById(kubernetesCluster.getTemplateId());
try {
if (originalState.equals(KubernetesCluster.State.Running)) {
plan(newVmRequiredCount, zone, clusterServiceOffering, kubernetesCluster.getDomainId(), kubernetesCluster.getAccountId(), clusterTemplate.getHypervisorType(), clusterTemplate.getArch());
plan(newVmRequiredCount, zone, clusterServiceOffering, kubernetesCluster.getDomainId(), kubernetesCluster.getAccountId(), clusterTemplate.getHypervisorType(), clusterTemplate.getArch(), nodeType);
} else {
plan(kubernetesCluster.getTotalNodeCount() + newVmRequiredCount, zone, clusterServiceOffering, kubernetesCluster.getDomainId(), kubernetesCluster.getAccountId(), clusterTemplate.getHypervisorType(), clusterTemplate.getArch());
plan(kubernetesCluster.getTotalNodeCount() + newVmRequiredCount, zone, clusterServiceOffering, kubernetesCluster.getDomainId(), kubernetesCluster.getAccountId(), clusterTemplate.getHypervisorType(), clusterTemplate.getArch(), nodeType);
}
} catch (InsufficientCapacityException e) {
logTransitStateToFailedIfNeededAndThrow(Level.WARN, String.format("Scaling failed for Kubernetes cluster : %s in zone : %s, insufficient capacity", kubernetesCluster.getName(), zone.getName()));
logTransitStateToFailedIfNeededAndThrow(Level.WARN, String.format("Scaling failed for Kubernetes cluster : %s in zone : %s, insufficient capacity: %s", kubernetesCluster.getName(), zone.getName(), e.getMessage()));
}
}
List<KubernetesClusterVmMapVO> vmList = kubernetesClusterVmMapDao.listByClusterId(kubernetesCluster.getId());
@ -465,10 +461,38 @@ public class KubernetesClusterScaleWorker extends KubernetesClusterResourceModif
return new ArrayList<>(workerVMsMap.subList(startIndex, totalWorkerNodes));
}
private void cleanupNewlyCreatedVms(Set<Long> originalVmIds) {
List<KubernetesClusterVmMapVO> currentVmMaps = kubernetesClusterVmMapDao.listByClusterId(kubernetesCluster.getId());
for (KubernetesClusterVmMapVO clusterVmMap : currentVmMaps) {
if (originalVmIds.contains(clusterVmMap.getVmId())) {
continue;
}
UserVmVO userVM = userVmDao.findById(clusterVmMap.getVmId());
if (Objects.isNull(userVM)) {
kubernetesClusterVmMapDao.expunge(clusterVmMap.getId());
continue;
}
logger.warn("Cleaning up VM {} created during failed scale-up of Kubernetes cluster {}", userVM, kubernetesCluster);
CallContext vmContext = CallContext.register(CallContext.current(), ApiCommandResourceType.VirtualMachine);
vmContext.setEventResourceId(userVM.getId());
try {
userVmService.destroyVm(userVM.getId(), true);
userVmManager.expunge(userVM);
} catch (Exception e) {
logger.warn("Failed to cleanup VM {} during scale-up rollback for Kubernetes cluster {}", userVM, kubernetesCluster, e);
} finally {
CallContext.unregister();
}
kubernetesClusterVmMapDao.expunge(clusterVmMap.getId());
}
}
private void scaleUpKubernetesClusterSize(final long newVmCount) throws CloudRuntimeException {
if (!kubernetesCluster.getState().equals(KubernetesCluster.State.Scaling)) {
stateTransitTo(kubernetesCluster.getId(), KubernetesCluster.Event.ScaleUpRequested);
}
Set<Long> originalVmIds = kubernetesClusterVmMapDao.listByClusterId(kubernetesCluster.getId())
.stream().map(KubernetesClusterVmMapVO::getVmId).collect(Collectors.toSet());
List<UserVm> clusterVMs = new ArrayList<>();
if (isDefaultTemplateUsed()) {
LaunchPermissionVO launchPermission = new LaunchPermissionVO(clusterTemplate.getId(), owner.getId());
@ -478,6 +502,7 @@ public class KubernetesClusterScaleWorker extends KubernetesClusterResourceModif
clusterVMs = provisionKubernetesClusterNodeVms((int)(newVmCount + kubernetesCluster.getNodeCount()), (int)kubernetesCluster.getNodeCount(), publicIpAddress, kubernetesCluster.getDomainId(), kubernetesCluster.getAccountId());
updateLoginUserDetails(clusterVMs.stream().map(InternalIdentity::getId).collect(Collectors.toList()));
} catch (CloudRuntimeException | ManagementServerException | ResourceUnavailableException | InsufficientCapacityException e) {
cleanupNewlyCreatedVms(originalVmIds);
logTransitStateToFailedIfNeededAndThrow(Level.ERROR, String.format("Scaling failed for Kubernetes cluster : %s, unable to provision node VM in the cluster", kubernetesCluster.getName()), e);
}
try {
@ -486,6 +511,7 @@ public class KubernetesClusterScaleWorker extends KubernetesClusterResourceModif
clusterVMIds.addAll(externalNodeIds);
scaleKubernetesClusterNetworkRules(clusterVMIds);
} catch (ManagementServerException e) {
cleanupNewlyCreatedVms(originalVmIds);
logTransitStateToFailedIfNeededAndThrow(Level.ERROR, String.format("Scaling failed for Kubernetes cluster : %s, unable to update network rules", kubernetesCluster.getName()), e);
}
attachIsoKubernetesVMs(clusterVMs);
@ -496,12 +522,13 @@ public class KubernetesClusterScaleWorker extends KubernetesClusterResourceModif
detachIsoKubernetesVMs(clusterVMs);
deleteTemplateLaunchPermission();
if (!readyNodesCountValid) { // Scaling failed
cleanupNewlyCreatedVms(originalVmIds);
logTransitStateToFailedIfNeededAndThrow(Level.ERROR, String.format("Scaling unsuccessful for Kubernetes cluster : %s as it does not have desired number of nodes in ready state", kubernetesCluster.getName()));
}
}
private void scaleKubernetesClusterSize(KubernetesClusterNodeType nodeType) throws CloudRuntimeException {
validateKubernetesClusterScaleSizeParameters();
validateKubernetesClusterScaleSizeParameters(nodeType);
final long originalClusterSize = kubernetesCluster.getNodeCount();
final long newVmRequiredCount = clusterSize - originalClusterSize;
if (KubernetesCluster.State.Created.equals(originalState)) {

View File

@ -270,7 +270,7 @@ public class KubernetesClusterStartWorker extends KubernetesClusterResourceModif
keypairs.add(kubernetesCluster.getKeyPair());
}
Long affinityGroupId = getExplicitAffinityGroup(domainId, accountId);
List<Long> affinityGroupIds = getMergedAffinityGroupIds(CONTROL, domainId, accountId);
String userDataDetails = kubernetesCluster.getCniConfigDetails();
if (kubernetesCluster.getSecurityGroupId() != null &&
networkModel.checkSecurityGroupSupportForNetwork(owner, zone, networkIds,
@ -279,15 +279,13 @@ public class KubernetesClusterStartWorker extends KubernetesClusterResourceModif
securityGroupIds.add(kubernetesCluster.getSecurityGroupId());
controlVm = userVmService.createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, controlNodeTemplate, networkIds, securityGroupIds, owner,
hostName, hostName, null, null, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST,base64UserData, userDataId, userDataDetails, keypairs,
requestedIps, addrs, null, null, Objects.nonNull(affinityGroupId) ?
Collections.singletonList(affinityGroupId) : null, customParameterMap, null, null, null,
requestedIps, addrs, null, null, affinityGroupIds, customParameterMap, null, null, null,
null, true, null, UserVmManager.CKS_NODE, null, null);
} else {
controlVm = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, controlNodeTemplate, networkIds, owner,
hostName, hostName, null, null, null, null,
Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, userDataId, userDataDetails, keypairs,
requestedIps, addrs, null, null, Objects.nonNull(affinityGroupId) ?
Collections.singletonList(affinityGroupId) : null, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE, null, null, null);
requestedIps, addrs, null, null, affinityGroupIds, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE, null, null, null);
}
if (logger.isInfoEnabled()) {
logger.info("Created control VM: {}, {} in the Kubernetes cluster: {}", controlVm, hostName, kubernetesCluster);
@ -439,7 +437,7 @@ public class KubernetesClusterStartWorker extends KubernetesClusterResourceModif
keypairs.add(kubernetesCluster.getKeyPair());
}
Long affinityGroupId = getExplicitAffinityGroup(domainId, accountId);
List<Long> affinityGroupIds = getMergedAffinityGroupIds(CONTROL, domainId, accountId);
if (kubernetesCluster.getSecurityGroupId() != null &&
networkModel.checkSecurityGroupSupportForNetwork(owner, zone, networkIds,
List.of(kubernetesCluster.getSecurityGroupId()))) {
@ -447,15 +445,13 @@ public class KubernetesClusterStartWorker extends KubernetesClusterResourceModif
securityGroupIds.add(kubernetesCluster.getSecurityGroupId());
additionalControlVm = userVmService.createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, controlNodeTemplate, networkIds, securityGroupIds, owner,
hostName, hostName, null, null, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST,base64UserData, null, null, keypairs,
null, addrs, null, null, Objects.nonNull(affinityGroupId) ?
Collections.singletonList(affinityGroupId) : null, customParameterMap, null, null, null,
null, addrs, null, null, affinityGroupIds, customParameterMap, null, null, null,
null, true, null, UserVmManager.CKS_NODE, null, null);
} else {
additionalControlVm = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, controlNodeTemplate, networkIds, owner,
hostName, hostName, null, null, null, null,
Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, null, null, keypairs,
null, addrs, null, null, Objects.nonNull(affinityGroupId) ?
Collections.singletonList(affinityGroupId) : null, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE, null, null, null);
null, addrs, null, null, affinityGroupIds, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE, null, null, null);
}
if (logger.isInfoEnabled()) {
@ -483,7 +479,7 @@ public class KubernetesClusterStartWorker extends KubernetesClusterResourceModif
if (StringUtils.isNotBlank(kubernetesCluster.getKeyPair())) {
keypairs.add(kubernetesCluster.getKeyPair());
}
Long affinityGroupId = getExplicitAffinityGroup(domainId, accountId);
List<Long> affinityGroupIds = getMergedAffinityGroupIds(ETCD, domainId, accountId);
String hostName = etcdNodeHostnames.get(etcdNodeIndex);
Map<String, String> customParameterMap = new HashMap<String, String>();
if (zone.isSecurityGroupEnabled()) {
@ -491,15 +487,13 @@ public class KubernetesClusterStartWorker extends KubernetesClusterResourceModif
securityGroupIds.add(kubernetesCluster.getSecurityGroupId());
etcdNode = userVmService.createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, etcdTemplate, networkIds, securityGroupIds, owner,
hostName, hostName, null, null, null, null, Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST,base64UserData, null, null, keypairs,
Map.of(kubernetesCluster.getNetworkId(), requestedIps.get(etcdNodeIndex)), addrs, null, null, Objects.nonNull(affinityGroupId) ?
Collections.singletonList(affinityGroupId) : null, customParameterMap, null, null, null,
Map.of(kubernetesCluster.getNetworkId(), requestedIps.get(etcdNodeIndex)), addrs, null, null, affinityGroupIds, customParameterMap, null, null, null,
null, true, null, null, null, null);
} else {
etcdNode = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, etcdTemplate, networkIds, owner,
hostName, hostName, null, null, null, null,
Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, null, null, keypairs,
Map.of(kubernetesCluster.getNetworkId(), requestedIps.get(etcdNodeIndex)), addrs, null, null, Objects.nonNull(affinityGroupId) ?
Collections.singletonList(affinityGroupId) : null, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE, null, null, null);
Map.of(kubernetesCluster.getNetworkId(), requestedIps.get(etcdNodeIndex)), addrs, null, null, affinityGroupIds, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE, null, null, null);
}
if (logger.isInfoEnabled()) {

View File

@ -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.kubernetes.cluster.dao;
import java.util.List;
import com.cloud.kubernetes.cluster.KubernetesClusterAffinityGroupMapVO;
import com.cloud.utils.db.GenericDao;
public interface KubernetesClusterAffinityGroupMapDao extends GenericDao<KubernetesClusterAffinityGroupMapVO, Long> {
List<KubernetesClusterAffinityGroupMapVO> listByClusterIdAndNodeType(long clusterId, String nodeType);
List<Long> listAffinityGroupIdsByClusterIdAndNodeType(long clusterId, String nodeType);
int removeByClusterId(long clusterId);
}

View File

@ -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.kubernetes.cluster.dao;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Component;
import com.cloud.kubernetes.cluster.KubernetesClusterAffinityGroupMapVO;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
@Component
public class KubernetesClusterAffinityGroupMapDaoImpl extends GenericDaoBase<KubernetesClusterAffinityGroupMapVO, Long>
implements KubernetesClusterAffinityGroupMapDao {
private final SearchBuilder<KubernetesClusterAffinityGroupMapVO> clusterIdAndNodeTypeSearch;
private final SearchBuilder<KubernetesClusterAffinityGroupMapVO> clusterIdSearch;
public KubernetesClusterAffinityGroupMapDaoImpl() {
clusterIdAndNodeTypeSearch = createSearchBuilder();
clusterIdAndNodeTypeSearch.and("clusterId", clusterIdAndNodeTypeSearch.entity().getClusterId(), SearchCriteria.Op.EQ);
clusterIdAndNodeTypeSearch.and("nodeType", clusterIdAndNodeTypeSearch.entity().getNodeType(), SearchCriteria.Op.EQ);
clusterIdAndNodeTypeSearch.done();
clusterIdSearch = createSearchBuilder();
clusterIdSearch.and("clusterId", clusterIdSearch.entity().getClusterId(), SearchCriteria.Op.EQ);
clusterIdSearch.done();
}
@Override
public List<KubernetesClusterAffinityGroupMapVO> listByClusterIdAndNodeType(long clusterId, String nodeType) {
SearchCriteria<KubernetesClusterAffinityGroupMapVO> sc = clusterIdAndNodeTypeSearch.create();
sc.setParameters("clusterId", clusterId);
sc.setParameters("nodeType", nodeType);
return listBy(sc);
}
@Override
public List<Long> listAffinityGroupIdsByClusterIdAndNodeType(long clusterId, String nodeType) {
List<KubernetesClusterAffinityGroupMapVO> maps = listByClusterIdAndNodeType(clusterId, nodeType);
if (CollectionUtils.isEmpty(maps)) {
return new ArrayList<>();
}
return maps.stream().map(KubernetesClusterAffinityGroupMapVO::getAffinityGroupId).collect(Collectors.toList());
}
@Override
public int removeByClusterId(long clusterId) {
SearchCriteria<KubernetesClusterAffinityGroupMapVO> sc = clusterIdSearch.create();
sc.setParameters("clusterId", clusterId);
return remove(sc);
}
}

View File

@ -17,6 +17,7 @@
package org.apache.cloudstack.api.command.user.kubernetes.cluster;
import java.security.InvalidParameterException;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -79,7 +80,7 @@ public class CreateKubernetesClusterCmd extends BaseAsyncCreateCmd {
@Inject
public KubernetesClusterService kubernetesClusterService;
@Inject
protected KubernetesServiceHelper kubernetesClusterHelper;
protected KubernetesServiceHelper kubernetesServiceHelper;
@Inject
private ConfigurationDao configurationDao;
@Inject
@ -125,6 +126,12 @@ public class CreateKubernetesClusterCmd extends BaseAsyncCreateCmd {
since = "4.21.0")
private Map<String, Map<String, String>> templateNodeTypeMap;
@ACL(accessType = AccessType.UseEntry)
@Parameter(name = ApiConstants.NODE_TYPE_AFFINITY_GROUP_MAP, type = CommandType.MAP,
description = "(Optional) Node Type to Affinity Group ID mapping. If provided, VMs of each node type will be added to the specified affinity group",
since = "4.23.0")
private Map<String, Map<String, String>> affinityGroupNodeTypeMap;
@ACL(accessType = AccessType.UseEntry)
@Parameter(name = ApiConstants.ETCD_NODES, type = CommandType.LONG,
description = "(Optional) Number of Kubernetes cluster etcd nodes, default is 0." +
@ -314,11 +321,15 @@ public class CreateKubernetesClusterCmd extends BaseAsyncCreateCmd {
}
public Map<String, Long> getServiceOfferingNodeTypeMap() {
return kubernetesClusterHelper.getServiceOfferingNodeTypeMap(serviceOfferingNodeTypeMap);
return kubernetesServiceHelper.getServiceOfferingNodeTypeMap(serviceOfferingNodeTypeMap);
}
public Map<String, Long> getTemplateNodeTypeMap() {
return kubernetesClusterHelper.getTemplateNodeTypeMap(templateNodeTypeMap);
return kubernetesServiceHelper.getTemplateNodeTypeMap(templateNodeTypeMap);
}
public Map<String, List<Long>> getAffinityGroupNodeTypeMap() {
return kubernetesServiceHelper.getAffinityGroupNodeTypeMap(affinityGroupNodeTypeMap);
}
public Hypervisor.HypervisorType getHypervisorType() {

View File

@ -57,7 +57,7 @@ public class ScaleKubernetesClusterCmd extends BaseAsyncCmd {
@Inject
public KubernetesClusterService kubernetesClusterService;
@Inject
protected KubernetesServiceHelper kubernetesClusterHelper;
protected KubernetesServiceHelper kubernetesServiceHelper;
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
@ -114,7 +114,7 @@ public class ScaleKubernetesClusterCmd extends BaseAsyncCmd {
}
public Map<String, Long> getServiceOfferingNodeTypeMap() {
return kubernetesClusterHelper.getServiceOfferingNodeTypeMap(this.serviceOfferingNodeTypeMap);
return kubernetesServiceHelper.getServiceOfferingNodeTypeMap(this.serviceOfferingNodeTypeMap);
}
public Long getClusterSize() {

View File

@ -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 org.apache.cloudstack.api.command.user.kubernetes.cluster;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.SecurityChecker;
import org.apache.cloudstack.api.ACL;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType;
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.ResponseObject;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.KubernetesClusterResponse;
import org.apache.cloudstack.context.CallContext;
import com.cloud.kubernetes.cluster.KubernetesCluster;
import com.cloud.kubernetes.cluster.KubernetesClusterService;
import com.cloud.kubernetes.cluster.KubernetesServiceHelper;
import com.cloud.utils.exception.CloudRuntimeException;
@APICommand(name = "updateKubernetesClusterAffinityGroups",
description = "Updates the affinity group mappings for a stopped Kubernetes cluster",
responseObject = KubernetesClusterResponse.class,
responseView = ResponseObject.ResponseView.Restricted,
entityType = {KubernetesCluster.class},
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = true,
since = "4.23.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
public class UpdateKubernetesClusterAffinityGroupCmd extends BaseCmd {
@Inject
public KubernetesClusterService kubernetesClusterService;
@Inject
protected KubernetesServiceHelper kubernetesServiceHelper;
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, required = true,
entityType = KubernetesClusterResponse.class,
description = "The ID of the Kubernetes cluster")
private Long id;
@ACL(accessType = SecurityChecker.AccessType.UseEntry)
@Parameter(name = ApiConstants.NODE_TYPE_AFFINITY_GROUP_MAP, type = CommandType.MAP,
description = "Node Type to Affinity Group ID mapping. VMs of each node type will be added to the specified affinity group",
since = "4.23.0")
private Map<String, Map<String, String>> affinityGroupNodeTypeMap;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
public Map<String, List<Long>> getAffinityGroupNodeTypeMap() {
return kubernetesServiceHelper.getAffinityGroupNodeTypeMap(affinityGroupNodeTypeMap);
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}
@Override
public ApiCommandResourceType getApiResourceType() {
return ApiCommandResourceType.KubernetesCluster;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public void execute() throws ServerApiException {
try {
if (!kubernetesClusterService.updateKubernetesClusterAffinityGroups(this)) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR,
String.format("Failed to update affinity groups for Kubernetes cluster ID: %d", getId()));
}
final KubernetesClusterResponse response = kubernetesClusterService.createKubernetesClusterResponse(getId());
response.setResponseName(getCommandName());
setResponseObject(response);
} catch (CloudRuntimeException exception) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, exception.getMessage());
}
}
}

View File

@ -220,6 +220,30 @@ public class KubernetesClusterResponse extends BaseResponseWithAnnotations imple
@Param(description = "The date when this Kubernetes cluster was created")
private Date created;
@SerializedName(ApiConstants.CONTROL_AFFINITY_GROUP_IDS)
@Param(description = "The IDs of affinity groups associated with control nodes", since = "4.23.0")
private String controlAffinityGroupIds;
@SerializedName(ApiConstants.CONTROL_AFFINITY_GROUP_NAMES)
@Param(description = "The names of affinity groups associated with control nodes", since = "4.23.0")
private String controlAffinityGroupNames;
@SerializedName(ApiConstants.WORKER_AFFINITY_GROUP_IDS)
@Param(description = "The IDs of affinity groups associated with worker nodes", since = "4.23.0")
private String workerAffinityGroupIds;
@SerializedName(ApiConstants.WORKER_AFFINITY_GROUP_NAMES)
@Param(description = "The names of affinity groups associated with worker nodes", since = "4.23.0")
private String workerAffinityGroupNames;
@SerializedName(ApiConstants.ETCD_AFFINITY_GROUP_IDS)
@Param(description = "The IDs of affinity groups associated with etcd nodes", since = "4.23.0")
private String etcdAffinityGroupIds;
@SerializedName(ApiConstants.ETCD_AFFINITY_GROUP_NAMES)
@Param(description = "The names of affinity groups associated with etcd nodes", since = "4.23.0")
private String etcdAffinityGroupNames;
public KubernetesClusterResponse() {
}
@ -535,4 +559,28 @@ public class KubernetesClusterResponse extends BaseResponseWithAnnotations imple
public void setCsiEnabled(Boolean csiEnabled) {
isCsiEnabled = csiEnabled;
}
public void setControlAffinityGroupIds(String controlAffinityGroupIds) {
this.controlAffinityGroupIds = controlAffinityGroupIds;
}
public void setControlAffinityGroupNames(String controlAffinityGroupNames) {
this.controlAffinityGroupNames = controlAffinityGroupNames;
}
public void setWorkerAffinityGroupIds(String workerAffinityGroupIds) {
this.workerAffinityGroupIds = workerAffinityGroupIds;
}
public void setWorkerAffinityGroupNames(String workerAffinityGroupNames) {
this.workerAffinityGroupNames = workerAffinityGroupNames;
}
public void setEtcdAffinityGroupIds(String etcdAffinityGroupIds) {
this.etcdAffinityGroupIds = etcdAffinityGroupIds;
}
public void setEtcdAffinityGroupNames(String etcdAffinityGroupNames) {
this.etcdAffinityGroupNames = etcdAffinityGroupNames;
}
}

View File

@ -32,6 +32,7 @@
<bean id="kubernetesClusterDaoImpl" class="com.cloud.kubernetes.cluster.dao.KubernetesClusterDaoImpl" />
<bean id="kubernetesClusterDetailsDaoImpl" class="com.cloud.kubernetes.cluster.dao.KubernetesClusterDetailsDaoImpl" />
<bean id="kubernetesClusterVmMapDaoImpl" class="com.cloud.kubernetes.cluster.dao.KubernetesClusterVmMapDaoImpl" />
<bean id="kubernetesClusterAffinityGroupMapDaoImpl" class="com.cloud.kubernetes.cluster.dao.KubernetesClusterAffinityGroupMapDaoImpl" />
<bean id="kubernetesClusterManagerImpl" class="com.cloud.kubernetes.cluster.KubernetesClusterManagerImpl" />
<bean id="kubernetesServiceHelper" class="com.cloud.kubernetes.cluster.KubernetesServiceHelperImpl" >

View File

@ -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.kubernetes.cluster;
import org.junit.Assert;
import org.junit.Test;
public class KubernetesClusterAffinityGroupMapVOTest {
@Test
public void testConstructorAndGetters() {
KubernetesClusterAffinityGroupMapVO vo =
new KubernetesClusterAffinityGroupMapVO(1L, "CONTROL", 100L);
Assert.assertEquals(1L, vo.getClusterId());
Assert.assertEquals("CONTROL", vo.getNodeType());
Assert.assertEquals(100L, vo.getAffinityGroupId());
}
@Test
public void testDefaultConstructor() {
KubernetesClusterAffinityGroupMapVO vo = new KubernetesClusterAffinityGroupMapVO();
Assert.assertNotNull(vo);
}
@Test
public void testSetClusterId() {
KubernetesClusterAffinityGroupMapVO vo = new KubernetesClusterAffinityGroupMapVO();
vo.setClusterId(2L);
Assert.assertEquals(2L, vo.getClusterId());
}
@Test
public void testSetNodeType() {
KubernetesClusterAffinityGroupMapVO vo = new KubernetesClusterAffinityGroupMapVO();
vo.setNodeType("WORKER");
Assert.assertEquals("WORKER", vo.getNodeType());
}
@Test
public void testSetAffinityGroupId() {
KubernetesClusterAffinityGroupMapVO vo = new KubernetesClusterAffinityGroupMapVO();
vo.setAffinityGroupId(200L);
Assert.assertEquals(200L, vo.getAffinityGroupId());
}
@Test
public void testAllNodeTypes() {
KubernetesClusterAffinityGroupMapVO controlVo =
new KubernetesClusterAffinityGroupMapVO(1L, "CONTROL", 10L);
KubernetesClusterAffinityGroupMapVO workerVo =
new KubernetesClusterAffinityGroupMapVO(1L, "WORKER", 20L);
KubernetesClusterAffinityGroupMapVO etcdVo =
new KubernetesClusterAffinityGroupMapVO(1L, "ETCD", 30L);
Assert.assertEquals("CONTROL", controlVo.getNodeType());
Assert.assertEquals("WORKER", workerVo.getNodeType());
Assert.assertEquals("ETCD", etcdVo.getNodeType());
}
@Test
public void testSettersChain() {
KubernetesClusterAffinityGroupMapVO vo = new KubernetesClusterAffinityGroupMapVO();
vo.setClusterId(5L);
vo.setNodeType("ETCD");
vo.setAffinityGroupId(500L);
Assert.assertEquals(5L, vo.getClusterId());
Assert.assertEquals("ETCD", vo.getNodeType());
Assert.assertEquals(500L, vo.getAffinityGroupId());
}
}

View File

@ -1,145 +0,0 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES 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.kubernetes.cluster;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.service.ServiceOfferingVO;
import com.cloud.service.dao.ServiceOfferingDao;
import com.cloud.vm.VmDetailConstants;
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.junit.MockitoJUnitRunner;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import static com.cloud.kubernetes.cluster.KubernetesServiceHelper.KubernetesClusterNodeType.CONTROL;
import static com.cloud.kubernetes.cluster.KubernetesServiceHelper.KubernetesClusterNodeType.ETCD;
import static com.cloud.kubernetes.cluster.KubernetesServiceHelper.KubernetesClusterNodeType.WORKER;
@RunWith(MockitoJUnitRunner.class)
public class KubernetesClusterHelperImplTest {
@Mock
private ServiceOfferingDao serviceOfferingDao;
@Mock
private ServiceOfferingVO workerServiceOffering;
@Mock
private ServiceOfferingVO controlServiceOffering;
@Mock
private ServiceOfferingVO etcdServiceOffering;
private static final String workerNodesOfferingId = UUID.randomUUID().toString();
private static final String controlNodesOfferingId = UUID.randomUUID().toString();
private static final String etcdNodesOfferingId = UUID.randomUUID().toString();
private static final Long workerOfferingId = 1L;
private static final Long controlOfferingId = 2L;
private static final Long etcdOfferingId = 3L;
private final KubernetesServiceHelperImpl helper = new KubernetesServiceHelperImpl();
@Before
public void setUp() {
helper.serviceOfferingDao = serviceOfferingDao;
Mockito.when(serviceOfferingDao.findByUuid(workerNodesOfferingId)).thenReturn(workerServiceOffering);
Mockito.when(serviceOfferingDao.findByUuid(controlNodesOfferingId)).thenReturn(controlServiceOffering);
Mockito.when(serviceOfferingDao.findByUuid(etcdNodesOfferingId)).thenReturn(etcdServiceOffering);
Mockito.when(workerServiceOffering.getId()).thenReturn(workerOfferingId);
Mockito.when(controlServiceOffering.getId()).thenReturn(controlOfferingId);
Mockito.when(etcdServiceOffering.getId()).thenReturn(etcdOfferingId);
}
@Test
public void testIsValidNodeTypeEmptyNodeType() {
Assert.assertFalse(helper.isValidNodeType(null));
}
@Test
public void testIsValidNodeTypeInvalidNodeType() {
String nodeType = "invalidNodeType";
Assert.assertFalse(helper.isValidNodeType(nodeType));
}
@Test
public void testIsValidNodeTypeValidNodeTypeLowercase() {
String nodeType = KubernetesServiceHelper.KubernetesClusterNodeType.WORKER.name().toLowerCase();
Assert.assertTrue(helper.isValidNodeType(nodeType));
}
private Map<String, String> createMapEntry(KubernetesServiceHelper.KubernetesClusterNodeType nodeType,
String nodeTypeOfferingUuid) {
Map<String, String> map = new HashMap<>();
map.put(VmDetailConstants.CKS_NODE_TYPE, nodeType.name().toLowerCase());
map.put(VmDetailConstants.OFFERING, nodeTypeOfferingUuid);
return map;
}
@Test
public void testNodeOfferingMap() {
Map<String, Map<String, String>> serviceOfferingNodeTypeMap = new HashMap<>();
Map<String, String> firstMap = createMapEntry(WORKER, workerNodesOfferingId);
Map<String, String> secondMap = createMapEntry(CONTROL, controlNodesOfferingId);
serviceOfferingNodeTypeMap.put("map1", firstMap);
serviceOfferingNodeTypeMap.put("map2", secondMap);
Map<String, Long> map = helper.getServiceOfferingNodeTypeMap(serviceOfferingNodeTypeMap);
Assert.assertNotNull(map);
Assert.assertEquals(2, map.size());
Assert.assertTrue(map.containsKey(WORKER.name()) && map.containsKey(CONTROL.name()));
Assert.assertEquals(workerOfferingId, map.get(WORKER.name()));
Assert.assertEquals(controlOfferingId, map.get(CONTROL.name()));
}
@Test
public void testNodeOfferingMapNullMap() {
Map<String, Long> map = helper.getServiceOfferingNodeTypeMap(null);
Assert.assertTrue(map.isEmpty());
}
@Test
public void testNodeOfferingMapEtcdNodes() {
Map<String, Map<String, String>> serviceOfferingNodeTypeMap = new HashMap<>();
Map<String, String> firstMap = createMapEntry(ETCD, etcdNodesOfferingId);
serviceOfferingNodeTypeMap.put("map1", firstMap);
Map<String, Long> map = helper.getServiceOfferingNodeTypeMap(serviceOfferingNodeTypeMap);
Assert.assertNotNull(map);
Assert.assertEquals(1, map.size());
Assert.assertTrue(map.containsKey(ETCD.name()));
Assert.assertEquals(etcdOfferingId, map.get(ETCD.name()));
}
@Test(expected = InvalidParameterValueException.class)
public void testCheckNodeTypeOfferingEntryCompletenessInvalidParameters() {
helper.checkNodeTypeOfferingEntryCompleteness(WORKER.name(), null);
}
@Test(expected = InvalidParameterValueException.class)
public void testCheckNodeTypeOfferingEntryValuesInvalidNodeType() {
String invalidNodeType = "invalidNodeTypeName";
helper.checkNodeTypeOfferingEntryValues(invalidNodeType, workerServiceOffering, workerNodesOfferingId);
}
@Test(expected = InvalidParameterValueException.class)
public void testCheckNodeTypeOfferingEntryValuesEmptyOffering() {
String nodeType = WORKER.name();
helper.checkNodeTypeOfferingEntryValues(nodeType, null, workerNodesOfferingId);
}
}

View File

@ -26,6 +26,7 @@ import com.cloud.dc.DataCenter;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.kubernetes.cluster.actionworkers.KubernetesClusterActionWorker;
import com.cloud.kubernetes.cluster.dao.KubernetesClusterAffinityGroupMapDao;
import com.cloud.kubernetes.cluster.dao.KubernetesClusterDao;
import com.cloud.kubernetes.cluster.dao.KubernetesClusterVmMapDao;
import com.cloud.kubernetes.version.KubernetesSupportedVersion;
@ -46,9 +47,14 @@ import com.cloud.utils.Pair;
import com.cloud.utils.net.NetUtils;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.dao.VMInstanceDao;
import com.cloud.host.HostVO;
import com.cloud.host.dao.HostDao;
import org.apache.cloudstack.affinity.AffinityGroupVO;
import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.command.user.kubernetes.cluster.AddVirtualMachinesToKubernetesClusterCmd;
import org.apache.cloudstack.api.command.user.kubernetes.cluster.RemoveVirtualMachinesFromKubernetesClusterCmd;
import org.apache.cloudstack.api.response.KubernetesClusterResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.commons.collections.MapUtils;
@ -103,6 +109,15 @@ public class KubernetesClusterManagerImplTest {
@Mock
private ServiceOfferingDao serviceOfferingDao;
@Mock
private KubernetesClusterAffinityGroupMapDao kubernetesClusterAffinityGroupMapDao;
@Mock
private AffinityGroupDao affinityGroupDao;
@Mock
private HostDao hostDao;
@Spy
@InjectMocks
KubernetesClusterManagerImpl kubernetesClusterManager;
@ -441,4 +456,462 @@ public class KubernetesClusterManagerImplTest {
String cksClusterPreferredArch = kubernetesClusterManager.getCksClusterPreferredArch(systemVMArch, cksIso);
Assert.assertEquals(CPU.CPUArch.amd64.getType(), cksClusterPreferredArch);
}
@Test
public void testSetAffinityGroupResponseForNodeTypeControl() {
KubernetesClusterResponse response = new KubernetesClusterResponse();
long clusterId = 1L;
AffinityGroupVO ag1 = Mockito.mock(AffinityGroupVO.class);
AffinityGroupVO ag2 = Mockito.mock(AffinityGroupVO.class);
Mockito.when(ag1.getUuid()).thenReturn("uuid-1");
Mockito.when(ag1.getName()).thenReturn("affinity-group-1");
Mockito.when(ag2.getUuid()).thenReturn("uuid-2");
Mockito.when(ag2.getName()).thenReturn("affinity-group-2");
Mockito.when(kubernetesClusterAffinityGroupMapDao.listAffinityGroupIdsByClusterIdAndNodeType(clusterId, CONTROL.name()))
.thenReturn(Arrays.asList(1L, 2L));
Mockito.when(affinityGroupDao.findById(1L)).thenReturn(ag1);
Mockito.when(affinityGroupDao.findById(2L)).thenReturn(ag2);
kubernetesClusterManager.setAffinityGroupResponseForNodeType(response, clusterId, CONTROL.name());
Mockito.verify(kubernetesClusterAffinityGroupMapDao).listAffinityGroupIdsByClusterIdAndNodeType(clusterId, CONTROL.name());
Mockito.verify(affinityGroupDao).findById(1L);
Mockito.verify(affinityGroupDao).findById(2L);
}
@Test
public void testSetAffinityGroupResponseForNodeTypeWorker() {
KubernetesClusterResponse response = new KubernetesClusterResponse();
long clusterId = 1L;
AffinityGroupVO ag = Mockito.mock(AffinityGroupVO.class);
Mockito.when(ag.getUuid()).thenReturn("worker-uuid");
Mockito.when(ag.getName()).thenReturn("worker-affinity");
Mockito.when(kubernetesClusterAffinityGroupMapDao.listAffinityGroupIdsByClusterIdAndNodeType(clusterId, WORKER.name()))
.thenReturn(Arrays.asList(10L));
Mockito.when(affinityGroupDao.findById(10L)).thenReturn(ag);
kubernetesClusterManager.setAffinityGroupResponseForNodeType(response, clusterId, WORKER.name());
Mockito.verify(kubernetesClusterAffinityGroupMapDao).listAffinityGroupIdsByClusterIdAndNodeType(clusterId, WORKER.name());
Mockito.verify(affinityGroupDao).findById(10L);
}
@Test
public void testSetAffinityGroupResponseForNodeTypeEtcd() {
KubernetesClusterResponse response = new KubernetesClusterResponse();
long clusterId = 1L;
AffinityGroupVO ag = Mockito.mock(AffinityGroupVO.class);
Mockito.when(ag.getUuid()).thenReturn("etcd-uuid");
Mockito.when(ag.getName()).thenReturn("etcd-affinity");
Mockito.when(kubernetesClusterAffinityGroupMapDao.listAffinityGroupIdsByClusterIdAndNodeType(clusterId, ETCD.name()))
.thenReturn(Arrays.asList(20L));
Mockito.when(affinityGroupDao.findById(20L)).thenReturn(ag);
kubernetesClusterManager.setAffinityGroupResponseForNodeType(response, clusterId, ETCD.name());
Mockito.verify(kubernetesClusterAffinityGroupMapDao).listAffinityGroupIdsByClusterIdAndNodeType(clusterId, ETCD.name());
Mockito.verify(affinityGroupDao).findById(20L);
}
@Test
public void testSetAffinityGroupResponseForNodeTypeEmptyList() {
KubernetesClusterResponse response = new KubernetesClusterResponse();
long clusterId = 1L;
Mockito.when(kubernetesClusterAffinityGroupMapDao.listAffinityGroupIdsByClusterIdAndNodeType(clusterId, CONTROL.name()))
.thenReturn(Collections.emptyList());
kubernetesClusterManager.setAffinityGroupResponseForNodeType(response, clusterId, CONTROL.name());
Mockito.verify(affinityGroupDao, Mockito.never()).findById(Mockito.anyLong());
}
@Test
public void testSetAffinityGroupResponseForNodeTypeNullList() {
KubernetesClusterResponse response = new KubernetesClusterResponse();
long clusterId = 1L;
Mockito.when(kubernetesClusterAffinityGroupMapDao.listAffinityGroupIdsByClusterIdAndNodeType(clusterId, ETCD.name()))
.thenReturn(null);
kubernetesClusterManager.setAffinityGroupResponseForNodeType(response, clusterId, ETCD.name());
Mockito.verify(affinityGroupDao, Mockito.never()).findById(Mockito.anyLong());
}
@Test
public void testSetAffinityGroupResponseForNodeTypeNullAffinityGroup() {
KubernetesClusterResponse response = new KubernetesClusterResponse();
long clusterId = 1L;
AffinityGroupVO ag1 = Mockito.mock(AffinityGroupVO.class);
Mockito.when(ag1.getUuid()).thenReturn("uuid-1");
Mockito.when(ag1.getName()).thenReturn("affinity-group-1");
Mockito.when(kubernetesClusterAffinityGroupMapDao.listAffinityGroupIdsByClusterIdAndNodeType(clusterId, CONTROL.name()))
.thenReturn(Arrays.asList(1L, 2L));
Mockito.when(affinityGroupDao.findById(1L)).thenReturn(ag1);
Mockito.when(affinityGroupDao.findById(2L)).thenReturn(null);
kubernetesClusterManager.setAffinityGroupResponseForNodeType(response, clusterId, CONTROL.name());
Mockito.verify(affinityGroupDao).findById(1L);
Mockito.verify(affinityGroupDao).findById(2L);
}
@Test
public void testSetNodeTypeAffinityGroupResponse() {
KubernetesClusterResponse response = new KubernetesClusterResponse();
long clusterId = 1L;
Mockito.when(kubernetesClusterAffinityGroupMapDao.listAffinityGroupIdsByClusterIdAndNodeType(Mockito.eq(clusterId), Mockito.anyString()))
.thenReturn(Collections.emptyList());
kubernetesClusterManager.setNodeTypeAffinityGroupResponse(response, clusterId);
Mockito.verify(kubernetesClusterAffinityGroupMapDao).listAffinityGroupIdsByClusterIdAndNodeType(clusterId, CONTROL.name());
Mockito.verify(kubernetesClusterAffinityGroupMapDao).listAffinityGroupIdsByClusterIdAndNodeType(clusterId, WORKER.name());
Mockito.verify(kubernetesClusterAffinityGroupMapDao).listAffinityGroupIdsByClusterIdAndNodeType(clusterId, ETCD.name());
}
@Test
public void testValidateNodeAffinityGroupsNoAffinityGroups() {
KubernetesCluster cluster = Mockito.mock(KubernetesCluster.class);
Mockito.when(cluster.getId()).thenReturn(1L);
List<Long> nodeIds = Arrays.asList(100L, 101L);
Mockito.when(kubernetesClusterAffinityGroupMapDao.listAffinityGroupIdsByClusterIdAndNodeType(1L, WORKER.name()))
.thenReturn(Collections.emptyList());
kubernetesClusterManager.validateNodeAffinityGroups(nodeIds, cluster);
Mockito.verify(kubernetesClusterVmMapDao, Mockito.never()).listByClusterIdAndVmType(Mockito.anyLong(), Mockito.any());
}
@Test
public void testValidateNodeAffinityGroupsNullAffinityGroups() {
KubernetesCluster cluster = Mockito.mock(KubernetesCluster.class);
Mockito.when(cluster.getId()).thenReturn(1L);
List<Long> nodeIds = Arrays.asList(100L, 101L);
Mockito.when(kubernetesClusterAffinityGroupMapDao.listAffinityGroupIdsByClusterIdAndNodeType(1L, WORKER.name()))
.thenReturn(null);
kubernetesClusterManager.validateNodeAffinityGroups(nodeIds, cluster);
Mockito.verify(kubernetesClusterVmMapDao, Mockito.never()).listByClusterIdAndVmType(Mockito.anyLong(), Mockito.any());
}
@Test(expected = InvalidParameterValueException.class)
public void testValidateNodeAffinityGroupsAntiAffinityNewNodeOnExistingHost() {
KubernetesCluster cluster = Mockito.mock(KubernetesCluster.class);
Mockito.when(cluster.getId()).thenReturn(1L);
Mockito.when(cluster.getName()).thenReturn("test-cluster");
Long newNodeId = 100L;
Long existingWorkerVmId = 200L;
Long sharedHostId = 1000L;
AffinityGroupVO affinityGroup = Mockito.mock(AffinityGroupVO.class);
Mockito.when(affinityGroup.getType()).thenReturn("host anti-affinity");
Mockito.when(affinityGroup.getName()).thenReturn("anti-affinity-group");
VMInstanceVO newNode = Mockito.mock(VMInstanceVO.class);
Mockito.when(newNode.getHostId()).thenReturn(sharedHostId);
Mockito.when(newNode.getInstanceName()).thenReturn("new-node-vm");
VMInstanceVO existingWorkerVm = Mockito.mock(VMInstanceVO.class);
Mockito.when(existingWorkerVm.getHostId()).thenReturn(sharedHostId);
KubernetesClusterVmMapVO workerVmMap = Mockito.mock(KubernetesClusterVmMapVO.class);
Mockito.when(workerVmMap.getVmId()).thenReturn(existingWorkerVmId);
HostVO host = Mockito.mock(HostVO.class);
Mockito.when(host.getName()).thenReturn("host-1");
Mockito.when(kubernetesClusterAffinityGroupMapDao.listAffinityGroupIdsByClusterIdAndNodeType(1L, WORKER.name()))
.thenReturn(Arrays.asList(10L));
Mockito.when(affinityGroupDao.findById(10L)).thenReturn(affinityGroup);
Mockito.when(kubernetesClusterVmMapDao.listByClusterIdAndVmType(1L, WORKER))
.thenReturn(Arrays.asList(workerVmMap));
Mockito.when(vmInstanceDao.findById(existingWorkerVmId)).thenReturn(existingWorkerVm);
Mockito.when(vmInstanceDao.findById(newNodeId)).thenReturn(newNode);
Mockito.when(hostDao.findById(sharedHostId)).thenReturn(host);
kubernetesClusterManager.validateNodeAffinityGroups(Arrays.asList(newNodeId), cluster);
}
@Test
public void testValidateNodeAffinityGroupsAntiAffinityNewNodeOnDifferentHost() {
KubernetesCluster cluster = Mockito.mock(KubernetesCluster.class);
Mockito.when(cluster.getId()).thenReturn(1L);
Mockito.lenient().when(cluster.getName()).thenReturn("test-cluster");
Long newNodeId = 100L;
Long existingWorkerVmId = 200L;
Long existingHostId = 1000L;
Long newNodeHostId = 1001L;
AffinityGroupVO affinityGroup = Mockito.mock(AffinityGroupVO.class);
Mockito.when(affinityGroup.getType()).thenReturn("host anti-affinity");
Mockito.lenient().when(affinityGroup.getName()).thenReturn("anti-affinity-group");
VMInstanceVO newNode = Mockito.mock(VMInstanceVO.class);
Mockito.when(newNode.getHostId()).thenReturn(newNodeHostId);
VMInstanceVO existingWorkerVm = Mockito.mock(VMInstanceVO.class);
Mockito.when(existingWorkerVm.getHostId()).thenReturn(existingHostId);
KubernetesClusterVmMapVO workerVmMap = Mockito.mock(KubernetesClusterVmMapVO.class);
Mockito.when(workerVmMap.getVmId()).thenReturn(existingWorkerVmId);
Mockito.when(kubernetesClusterAffinityGroupMapDao.listAffinityGroupIdsByClusterIdAndNodeType(1L, WORKER.name()))
.thenReturn(Arrays.asList(10L));
Mockito.when(affinityGroupDao.findById(10L)).thenReturn(affinityGroup);
Mockito.when(kubernetesClusterVmMapDao.listByClusterIdAndVmType(1L, WORKER))
.thenReturn(Arrays.asList(workerVmMap));
Mockito.when(vmInstanceDao.findById(existingWorkerVmId)).thenReturn(existingWorkerVm);
Mockito.when(vmInstanceDao.findById(newNodeId)).thenReturn(newNode);
kubernetesClusterManager.validateNodeAffinityGroups(Arrays.asList(newNodeId), cluster);
Mockito.verify(kubernetesClusterAffinityGroupMapDao).listAffinityGroupIdsByClusterIdAndNodeType(1L, WORKER.name());
}
@Test
public void testValidateNodeAffinityGroupsAffinityNewNodeOnSameHost() {
KubernetesCluster cluster = Mockito.mock(KubernetesCluster.class);
Mockito.when(cluster.getId()).thenReturn(1L);
Mockito.lenient().when(cluster.getName()).thenReturn("test-cluster");
Long newNodeId = 100L;
Long existingWorkerVmId = 200L;
Long sharedHostId = 1000L;
AffinityGroupVO affinityGroup = Mockito.mock(AffinityGroupVO.class);
Mockito.when(affinityGroup.getType()).thenReturn("host affinity");
Mockito.lenient().when(affinityGroup.getName()).thenReturn("affinity-group");
VMInstanceVO newNode = Mockito.mock(VMInstanceVO.class);
Mockito.when(newNode.getHostId()).thenReturn(sharedHostId);
VMInstanceVO existingWorkerVm = Mockito.mock(VMInstanceVO.class);
Mockito.when(existingWorkerVm.getHostId()).thenReturn(sharedHostId);
KubernetesClusterVmMapVO workerVmMap = Mockito.mock(KubernetesClusterVmMapVO.class);
Mockito.when(workerVmMap.getVmId()).thenReturn(existingWorkerVmId);
Mockito.when(kubernetesClusterAffinityGroupMapDao.listAffinityGroupIdsByClusterIdAndNodeType(1L, WORKER.name()))
.thenReturn(Arrays.asList(10L));
Mockito.when(affinityGroupDao.findById(10L)).thenReturn(affinityGroup);
Mockito.when(kubernetesClusterVmMapDao.listByClusterIdAndVmType(1L, WORKER))
.thenReturn(Arrays.asList(workerVmMap));
Mockito.when(vmInstanceDao.findById(existingWorkerVmId)).thenReturn(existingWorkerVm);
Mockito.when(vmInstanceDao.findById(newNodeId)).thenReturn(newNode);
kubernetesClusterManager.validateNodeAffinityGroups(Arrays.asList(newNodeId), cluster);
Mockito.verify(kubernetesClusterAffinityGroupMapDao).listAffinityGroupIdsByClusterIdAndNodeType(1L, WORKER.name());
}
@Test(expected = InvalidParameterValueException.class)
public void testValidateNodeAffinityGroupsAffinityNewNodeOnDifferentHost() {
KubernetesCluster cluster = Mockito.mock(KubernetesCluster.class);
Mockito.when(cluster.getId()).thenReturn(1L);
Mockito.when(cluster.getName()).thenReturn("test-cluster");
Long newNodeId = 100L;
Long existingWorkerVmId = 200L;
Long existingHostId = 1000L;
Long newNodeHostId = 1001L;
AffinityGroupVO affinityGroup = Mockito.mock(AffinityGroupVO.class);
Mockito.when(affinityGroup.getType()).thenReturn("host affinity");
Mockito.when(affinityGroup.getName()).thenReturn("affinity-group");
VMInstanceVO newNode = Mockito.mock(VMInstanceVO.class);
Mockito.when(newNode.getHostId()).thenReturn(newNodeHostId);
Mockito.when(newNode.getInstanceName()).thenReturn("new-node-vm");
VMInstanceVO existingWorkerVm = Mockito.mock(VMInstanceVO.class);
Mockito.when(existingWorkerVm.getHostId()).thenReturn(existingHostId);
KubernetesClusterVmMapVO workerVmMap = Mockito.mock(KubernetesClusterVmMapVO.class);
Mockito.when(workerVmMap.getVmId()).thenReturn(existingWorkerVmId);
HostVO newHost = Mockito.mock(HostVO.class);
Mockito.when(newHost.getName()).thenReturn("host-2");
HostVO existingHost = Mockito.mock(HostVO.class);
Mockito.when(existingHost.getName()).thenReturn("host-1");
Mockito.when(kubernetesClusterAffinityGroupMapDao.listAffinityGroupIdsByClusterIdAndNodeType(1L, WORKER.name()))
.thenReturn(Arrays.asList(10L));
Mockito.when(affinityGroupDao.findById(10L)).thenReturn(affinityGroup);
Mockito.when(kubernetesClusterVmMapDao.listByClusterIdAndVmType(1L, WORKER))
.thenReturn(Arrays.asList(workerVmMap));
Mockito.when(vmInstanceDao.findById(existingWorkerVmId)).thenReturn(existingWorkerVm);
Mockito.when(vmInstanceDao.findById(newNodeId)).thenReturn(newNode);
Mockito.when(hostDao.findById(newNodeHostId)).thenReturn(newHost);
Mockito.when(hostDao.findById(existingHostId)).thenReturn(existingHost);
kubernetesClusterManager.validateNodeAffinityGroups(Arrays.asList(newNodeId), cluster);
}
@Test(expected = InvalidParameterValueException.class)
public void testValidateNodeAffinityGroupsAntiAffinityMultipleNewNodesOnSameHost() {
KubernetesCluster cluster = Mockito.mock(KubernetesCluster.class);
Mockito.when(cluster.getId()).thenReturn(1L);
Mockito.when(cluster.getName()).thenReturn("test-cluster");
Long newNodeId1 = 100L;
Long newNodeId2 = 101L;
Long sharedHostId = 1000L;
AffinityGroupVO affinityGroup = Mockito.mock(AffinityGroupVO.class);
Mockito.lenient().when(affinityGroup.getType()).thenReturn("host anti-affinity");
Mockito.when(affinityGroup.getName()).thenReturn("anti-affinity-group");
VMInstanceVO newNode1 = Mockito.mock(VMInstanceVO.class);
Mockito.when(newNode1.getHostId()).thenReturn(sharedHostId);
Mockito.lenient().when(newNode1.getInstanceName()).thenReturn("new-node-vm-1");
VMInstanceVO newNode2 = Mockito.mock(VMInstanceVO.class);
Mockito.when(newNode2.getHostId()).thenReturn(sharedHostId);
Mockito.when(newNode2.getInstanceName()).thenReturn("new-node-vm-2");
HostVO host = Mockito.mock(HostVO.class);
Mockito.when(host.getName()).thenReturn("host-1");
Mockito.when(kubernetesClusterAffinityGroupMapDao.listAffinityGroupIdsByClusterIdAndNodeType(1L, WORKER.name()))
.thenReturn(Arrays.asList(10L));
Mockito.when(affinityGroupDao.findById(10L)).thenReturn(affinityGroup);
Mockito.when(kubernetesClusterVmMapDao.listByClusterIdAndVmType(1L, WORKER))
.thenReturn(Collections.emptyList());
Mockito.when(vmInstanceDao.findById(newNodeId1)).thenReturn(newNode1);
Mockito.when(vmInstanceDao.findById(newNodeId2)).thenReturn(newNode2);
Mockito.when(hostDao.findById(sharedHostId)).thenReturn(host);
kubernetesClusterManager.validateNodeAffinityGroups(Arrays.asList(newNodeId1, newNodeId2), cluster);
}
@Test
public void testValidateNodeAffinityGroupsAntiAffinityMultipleNewNodesOnDifferentHosts() {
KubernetesCluster cluster = Mockito.mock(KubernetesCluster.class);
Mockito.when(cluster.getId()).thenReturn(1L);
Mockito.lenient().when(cluster.getName()).thenReturn("test-cluster");
Long newNodeId1 = 100L;
Long newNodeId2 = 101L;
Long hostId1 = 1000L;
Long hostId2 = 1001L;
AffinityGroupVO affinityGroup = Mockito.mock(AffinityGroupVO.class);
Mockito.lenient().when(affinityGroup.getType()).thenReturn("host anti-affinity");
Mockito.lenient().when(affinityGroup.getName()).thenReturn("anti-affinity-group");
VMInstanceVO newNode1 = Mockito.mock(VMInstanceVO.class);
Mockito.when(newNode1.getHostId()).thenReturn(hostId1);
VMInstanceVO newNode2 = Mockito.mock(VMInstanceVO.class);
Mockito.when(newNode2.getHostId()).thenReturn(hostId2);
Mockito.when(kubernetesClusterAffinityGroupMapDao.listAffinityGroupIdsByClusterIdAndNodeType(1L, WORKER.name()))
.thenReturn(Arrays.asList(10L));
Mockito.when(affinityGroupDao.findById(10L)).thenReturn(affinityGroup);
Mockito.when(kubernetesClusterVmMapDao.listByClusterIdAndVmType(1L, WORKER))
.thenReturn(Collections.emptyList());
Mockito.when(vmInstanceDao.findById(newNodeId1)).thenReturn(newNode1);
Mockito.when(vmInstanceDao.findById(newNodeId2)).thenReturn(newNode2);
kubernetesClusterManager.validateNodeAffinityGroups(Arrays.asList(newNodeId1, newNodeId2), cluster);
Mockito.verify(kubernetesClusterAffinityGroupMapDao).listAffinityGroupIdsByClusterIdAndNodeType(1L, WORKER.name());
}
@Test
public void testValidateNodeAffinityGroupsNodeWithNullHost() {
KubernetesCluster cluster = Mockito.mock(KubernetesCluster.class);
Mockito.when(cluster.getId()).thenReturn(1L);
Mockito.lenient().when(cluster.getName()).thenReturn("test-cluster");
Long newNodeId = 100L;
AffinityGroupVO affinityGroup = Mockito.mock(AffinityGroupVO.class);
Mockito.lenient().when(affinityGroup.getType()).thenReturn("host anti-affinity");
Mockito.lenient().when(affinityGroup.getName()).thenReturn("anti-affinity-group");
VMInstanceVO newNode = Mockito.mock(VMInstanceVO.class);
Mockito.when(newNode.getHostId()).thenReturn(null);
Mockito.when(kubernetesClusterAffinityGroupMapDao.listAffinityGroupIdsByClusterIdAndNodeType(1L, WORKER.name()))
.thenReturn(Arrays.asList(10L));
Mockito.when(affinityGroupDao.findById(10L)).thenReturn(affinityGroup);
Mockito.when(kubernetesClusterVmMapDao.listByClusterIdAndVmType(1L, WORKER))
.thenReturn(Collections.emptyList());
Mockito.when(vmInstanceDao.findById(newNodeId)).thenReturn(newNode);
kubernetesClusterManager.validateNodeAffinityGroups(Arrays.asList(newNodeId), cluster);
Mockito.verify(vmInstanceDao, Mockito.atLeastOnce()).findById(newNodeId);
}
@Test
public void testValidateNodeAffinityGroupsNullNode() {
KubernetesCluster cluster = Mockito.mock(KubernetesCluster.class);
Mockito.when(cluster.getId()).thenReturn(1L);
Mockito.lenient().when(cluster.getName()).thenReturn("test-cluster");
Long newNodeId = 100L;
AffinityGroupVO affinityGroup = Mockito.mock(AffinityGroupVO.class);
Mockito.lenient().when(affinityGroup.getType()).thenReturn("host anti-affinity");
Mockito.lenient().when(affinityGroup.getName()).thenReturn("anti-affinity-group");
Mockito.when(kubernetesClusterAffinityGroupMapDao.listAffinityGroupIdsByClusterIdAndNodeType(1L, WORKER.name()))
.thenReturn(Arrays.asList(10L));
Mockito.when(affinityGroupDao.findById(10L)).thenReturn(affinityGroup);
Mockito.when(kubernetesClusterVmMapDao.listByClusterIdAndVmType(1L, WORKER))
.thenReturn(Collections.emptyList());
Mockito.when(vmInstanceDao.findById(newNodeId)).thenReturn(null);
kubernetesClusterManager.validateNodeAffinityGroups(Arrays.asList(newNodeId), cluster);
Mockito.verify(vmInstanceDao, Mockito.atLeastOnce()).findById(newNodeId);
}
@Test
public void testValidateNodeAffinityGroupsAffinityNoExistingWorkers() {
KubernetesCluster cluster = Mockito.mock(KubernetesCluster.class);
Mockito.when(cluster.getId()).thenReturn(1L);
Mockito.lenient().when(cluster.getName()).thenReturn("test-cluster");
Long newNodeId = 100L;
Long newNodeHostId = 1000L;
AffinityGroupVO affinityGroup = Mockito.mock(AffinityGroupVO.class);
Mockito.lenient().when(affinityGroup.getType()).thenReturn("host affinity");
Mockito.lenient().when(affinityGroup.getName()).thenReturn("affinity-group");
VMInstanceVO newNode = Mockito.mock(VMInstanceVO.class);
Mockito.when(newNode.getHostId()).thenReturn(newNodeHostId);
Mockito.when(kubernetesClusterAffinityGroupMapDao.listAffinityGroupIdsByClusterIdAndNodeType(1L, WORKER.name()))
.thenReturn(Arrays.asList(10L));
Mockito.when(affinityGroupDao.findById(10L)).thenReturn(affinityGroup);
Mockito.when(kubernetesClusterVmMapDao.listByClusterIdAndVmType(1L, WORKER))
.thenReturn(Collections.emptyList());
Mockito.when(vmInstanceDao.findById(newNodeId)).thenReturn(newNode);
kubernetesClusterManager.validateNodeAffinityGroups(Arrays.asList(newNodeId), cluster);
Mockito.verify(kubernetesClusterAffinityGroupMapDao).listAffinityGroupIdsByClusterIdAndNodeType(1L, WORKER.name());
}
}

View File

@ -17,6 +17,15 @@
package com.cloud.kubernetes.cluster;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.apache.cloudstack.affinity.AffinityGroupVO;
import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
@ -24,11 +33,16 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.kubernetes.cluster.KubernetesServiceHelper.KubernetesClusterNodeType;
import com.cloud.kubernetes.cluster.dao.KubernetesClusterDao;
import com.cloud.kubernetes.cluster.dao.KubernetesClusterVmMapDao;
import com.cloud.service.ServiceOfferingVO;
import com.cloud.service.dao.ServiceOfferingDao;
import com.cloud.uservm.UserVm;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.UserVmManager;
import com.cloud.vm.VmDetailConstants;
@RunWith(MockitoJUnitRunner.class)
public class KubernetesServiceHelperImplTest {
@ -36,6 +50,10 @@ public class KubernetesServiceHelperImplTest {
KubernetesClusterVmMapDao kubernetesClusterVmMapDao;
@Mock
KubernetesClusterDao kubernetesClusterDao;
@Mock
AffinityGroupDao affinityGroupDao;
@Mock
ServiceOfferingDao serviceOfferingDao;
@InjectMocks
KubernetesServiceHelperImpl kubernetesServiceHelper = new KubernetesServiceHelperImpl();
@ -84,4 +102,302 @@ public class KubernetesServiceHelperImplTest {
Mockito.when(kubernetesCluster.getClusterType()).thenReturn(KubernetesCluster.ClusterType.ExternalManaged);
kubernetesServiceHelper.checkVmCanBeDestroyed(vm);
}
@Test
public void testIsValidNodeTypeEmptyNodeType() {
Assert.assertFalse(kubernetesServiceHelper.isValidNodeType(null));
}
@Test
public void testIsValidNodeTypeInvalidNodeType() {
Assert.assertFalse(kubernetesServiceHelper.isValidNodeType("invalidNodeType"));
}
@Test
public void testIsValidNodeTypeValidNodeTypeLowercase() {
String nodeType = KubernetesClusterNodeType.WORKER.name().toLowerCase();
Assert.assertTrue(kubernetesServiceHelper.isValidNodeType(nodeType));
}
private Map<String, String> createServiceOfferingMapEntry(KubernetesClusterNodeType nodeType, String offeringUuid) {
Map<String, String> map = new HashMap<>();
map.put(VmDetailConstants.CKS_NODE_TYPE, nodeType.name().toLowerCase());
map.put(VmDetailConstants.OFFERING, offeringUuid);
return map;
}
@Test
public void testGetServiceOfferingNodeTypeMap() {
String workerOfferingUuid = UUID.randomUUID().toString();
String controlOfferingUuid = UUID.randomUUID().toString();
ServiceOfferingVO workerOffering = Mockito.mock(ServiceOfferingVO.class);
Mockito.when(workerOffering.getId()).thenReturn(1L);
Mockito.when(serviceOfferingDao.findByUuid(workerOfferingUuid)).thenReturn(workerOffering);
ServiceOfferingVO controlOffering = Mockito.mock(ServiceOfferingVO.class);
Mockito.when(controlOffering.getId()).thenReturn(2L);
Mockito.when(serviceOfferingDao.findByUuid(controlOfferingUuid)).thenReturn(controlOffering);
Map<String, Map<String, String>> serviceOfferingNodeTypeMap = new HashMap<>();
serviceOfferingNodeTypeMap.put("map1", createServiceOfferingMapEntry(KubernetesClusterNodeType.WORKER, workerOfferingUuid));
serviceOfferingNodeTypeMap.put("map2", createServiceOfferingMapEntry(KubernetesClusterNodeType.CONTROL, controlOfferingUuid));
Map<String, Long> result = kubernetesServiceHelper.getServiceOfferingNodeTypeMap(serviceOfferingNodeTypeMap);
Assert.assertNotNull(result);
Assert.assertEquals(2, result.size());
Assert.assertTrue(result.containsKey(KubernetesClusterNodeType.WORKER.name()));
Assert.assertTrue(result.containsKey(KubernetesClusterNodeType.CONTROL.name()));
Assert.assertEquals(Long.valueOf(1L), result.get(KubernetesClusterNodeType.WORKER.name()));
Assert.assertEquals(Long.valueOf(2L), result.get(KubernetesClusterNodeType.CONTROL.name()));
}
@Test
public void testGetServiceOfferingNodeTypeMapNullMap() {
Map<String, Long> result = kubernetesServiceHelper.getServiceOfferingNodeTypeMap(null);
Assert.assertTrue(result.isEmpty());
}
@Test
public void testGetServiceOfferingNodeTypeMapEtcdNodes() {
String etcdOfferingUuid = UUID.randomUUID().toString();
ServiceOfferingVO etcdOffering = Mockito.mock(ServiceOfferingVO.class);
Mockito.when(etcdOffering.getId()).thenReturn(3L);
Mockito.when(serviceOfferingDao.findByUuid(etcdOfferingUuid)).thenReturn(etcdOffering);
Map<String, Map<String, String>> serviceOfferingNodeTypeMap = new HashMap<>();
serviceOfferingNodeTypeMap.put("map1", createServiceOfferingMapEntry(KubernetesClusterNodeType.ETCD, etcdOfferingUuid));
Map<String, Long> result = kubernetesServiceHelper.getServiceOfferingNodeTypeMap(serviceOfferingNodeTypeMap);
Assert.assertNotNull(result);
Assert.assertEquals(1, result.size());
Assert.assertTrue(result.containsKey(KubernetesClusterNodeType.ETCD.name()));
Assert.assertEquals(Long.valueOf(3L), result.get(KubernetesClusterNodeType.ETCD.name()));
}
@Test(expected = InvalidParameterValueException.class)
public void testCheckNodeTypeOfferingEntryCompletenessInvalidParameters() {
kubernetesServiceHelper.checkNodeTypeOfferingEntryCompleteness(KubernetesClusterNodeType.WORKER.name(), null);
}
@Test(expected = InvalidParameterValueException.class)
public void testCheckNodeTypeOfferingEntryValuesInvalidNodeType() {
ServiceOfferingVO offering = Mockito.mock(ServiceOfferingVO.class);
kubernetesServiceHelper.checkNodeTypeOfferingEntryValues("invalidNodeTypeName", offering, "some-uuid");
}
@Test(expected = InvalidParameterValueException.class)
public void testCheckNodeTypeOfferingEntryValuesEmptyOffering() {
kubernetesServiceHelper.checkNodeTypeOfferingEntryValues(KubernetesClusterNodeType.WORKER.name(), null, "some-uuid");
}
@Test(expected = InvalidParameterValueException.class)
public void testCheckNodeTypeAffinityGroupEntryCompletenessBlankNodeType() {
kubernetesServiceHelper.checkNodeTypeAffinityGroupEntryCompleteness("", "affinity-group-uuid");
}
@Test(expected = InvalidParameterValueException.class)
public void testCheckNodeTypeAffinityGroupEntryCompletenessBlankAffinityGroupUuid() {
kubernetesServiceHelper.checkNodeTypeAffinityGroupEntryCompleteness("control", "");
}
@Test
public void testCheckNodeTypeAffinityGroupEntryCompletenessValid() {
kubernetesServiceHelper.checkNodeTypeAffinityGroupEntryCompleteness("control", "affinity-group-uuid");
}
@Test(expected = InvalidParameterValueException.class)
public void testCheckNodeTypeAffinityGroupEntryNodeTypeInvalid() {
kubernetesServiceHelper.checkNodeTypeAffinityGroupEntryNodeType("invalid-node-type");
}
@Test
public void testCheckNodeTypeAffinityGroupEntryNodeTypeValid() {
kubernetesServiceHelper.checkNodeTypeAffinityGroupEntryNodeType("control");
}
@Test(expected = InvalidParameterValueException.class)
public void testValidateAffinityGroupUuidAndGetIdBlank() {
kubernetesServiceHelper.validateAffinityGroupUuidAndGetId("");
}
@Test(expected = InvalidParameterValueException.class)
public void testValidateAffinityGroupUuidAndGetIdNotFound() {
Mockito.when(affinityGroupDao.findByUuid("non-existent-uuid")).thenReturn(null);
kubernetesServiceHelper.validateAffinityGroupUuidAndGetId("non-existent-uuid");
}
@Test
public void testValidateAffinityGroupUuidAndGetIdValid() {
AffinityGroupVO affinityGroup = Mockito.mock(AffinityGroupVO.class);
Mockito.when(affinityGroup.getId()).thenReturn(100L);
Mockito.when(affinityGroupDao.findByUuid("valid-uuid")).thenReturn(affinityGroup);
Long result = kubernetesServiceHelper.validateAffinityGroupUuidAndGetId("valid-uuid");
Assert.assertEquals(Long.valueOf(100L), result);
}
@Test
public void testValidateAndGetAffinityGroupIdsSingleUuid() {
AffinityGroupVO affinityGroup = Mockito.mock(AffinityGroupVO.class);
Mockito.when(affinityGroup.getId()).thenReturn(1L);
Mockito.when(affinityGroupDao.findByUuid("uuid1")).thenReturn(affinityGroup);
List<Long> result = kubernetesServiceHelper.validateAndGetAffinityGroupIds("uuid1");
Assert.assertEquals(1, result.size());
Assert.assertEquals(Long.valueOf(1L), result.get(0));
}
@Test
public void testValidateAndGetAffinityGroupIdsMultipleUuids() {
AffinityGroupVO affinityGroup1 = Mockito.mock(AffinityGroupVO.class);
AffinityGroupVO affinityGroup2 = Mockito.mock(AffinityGroupVO.class);
AffinityGroupVO affinityGroup3 = Mockito.mock(AffinityGroupVO.class);
Mockito.when(affinityGroup1.getId()).thenReturn(1L);
Mockito.when(affinityGroup2.getId()).thenReturn(2L);
Mockito.when(affinityGroup3.getId()).thenReturn(3L);
Mockito.when(affinityGroupDao.findByUuid("uuid1")).thenReturn(affinityGroup1);
Mockito.when(affinityGroupDao.findByUuid("uuid2")).thenReturn(affinityGroup2);
Mockito.when(affinityGroupDao.findByUuid("uuid3")).thenReturn(affinityGroup3);
List<Long> result = kubernetesServiceHelper.validateAndGetAffinityGroupIds("uuid1,uuid2,uuid3");
Assert.assertEquals(3, result.size());
Assert.assertEquals(Arrays.asList(1L, 2L, 3L), result);
}
@Test
public void testValidateAndGetAffinityGroupIdsWithSpaces() {
AffinityGroupVO affinityGroup1 = Mockito.mock(AffinityGroupVO.class);
AffinityGroupVO affinityGroup2 = Mockito.mock(AffinityGroupVO.class);
Mockito.when(affinityGroup1.getId()).thenReturn(1L);
Mockito.when(affinityGroup2.getId()).thenReturn(2L);
Mockito.when(affinityGroupDao.findByUuid("uuid1")).thenReturn(affinityGroup1);
Mockito.when(affinityGroupDao.findByUuid("uuid2")).thenReturn(affinityGroup2);
List<Long> result = kubernetesServiceHelper.validateAndGetAffinityGroupIds(" uuid1 , uuid2 ");
Assert.assertEquals(2, result.size());
Assert.assertEquals(Arrays.asList(1L, 2L), result);
}
@Test(expected = InvalidParameterValueException.class)
public void testValidateAndGetAffinityGroupIdsOneInvalid() {
AffinityGroupVO affinityGroup1 = Mockito.mock(AffinityGroupVO.class);
Mockito.when(affinityGroupDao.findByUuid("uuid1")).thenReturn(affinityGroup1);
Mockito.when(affinityGroupDao.findByUuid("invalid-uuid")).thenReturn(null);
kubernetesServiceHelper.validateAndGetAffinityGroupIds("uuid1,invalid-uuid");
}
@Test
public void testAddNodeTypeAffinityGroupEntry() {
Map<String, List<Long>> mapping = new HashMap<>();
kubernetesServiceHelper.addNodeTypeAffinityGroupEntry("control", Arrays.asList(1L, 2L), mapping);
Assert.assertEquals(1, mapping.size());
Assert.assertEquals(Arrays.asList(1L, 2L), mapping.get("CONTROL"));
}
@Test
public void testProcessNodeTypeAffinityGroupEntryAndAddToMappingIfValidEmptyEntry() {
Map<String, List<Long>> mapping = new HashMap<>();
kubernetesServiceHelper.processNodeTypeAffinityGroupEntryAndAddToMappingIfValid(new HashMap<>(), mapping);
Assert.assertTrue(mapping.isEmpty());
}
@Test
public void testProcessNodeTypeAffinityGroupEntryAndAddToMappingIfValidValidEntry() {
AffinityGroupVO affinityGroup = Mockito.mock(AffinityGroupVO.class);
Mockito.when(affinityGroup.getId()).thenReturn(100L);
Mockito.when(affinityGroupDao.findByUuid("affinity-group-uuid")).thenReturn(affinityGroup);
Map<String, String> entry = new HashMap<>();
entry.put(VmDetailConstants.CKS_NODE_TYPE, "control");
entry.put(VmDetailConstants.AFFINITY_GROUP, "affinity-group-uuid");
Map<String, List<Long>> mapping = new HashMap<>();
kubernetesServiceHelper.processNodeTypeAffinityGroupEntryAndAddToMappingIfValid(entry, mapping);
Assert.assertEquals(1, mapping.size());
Assert.assertEquals(Arrays.asList(100L), mapping.get("CONTROL"));
}
@Test
public void testProcessNodeTypeAffinityGroupEntryAndAddToMappingIfValidMultipleUuids() {
AffinityGroupVO affinityGroup1 = Mockito.mock(AffinityGroupVO.class);
AffinityGroupVO affinityGroup2 = Mockito.mock(AffinityGroupVO.class);
Mockito.when(affinityGroup1.getId()).thenReturn(1L);
Mockito.when(affinityGroup2.getId()).thenReturn(2L);
Mockito.when(affinityGroupDao.findByUuid("uuid1")).thenReturn(affinityGroup1);
Mockito.when(affinityGroupDao.findByUuid("uuid2")).thenReturn(affinityGroup2);
Map<String, String> entry = new HashMap<>();
entry.put(VmDetailConstants.CKS_NODE_TYPE, "worker");
entry.put(VmDetailConstants.AFFINITY_GROUP, "uuid1,uuid2");
Map<String, List<Long>> mapping = new HashMap<>();
kubernetesServiceHelper.processNodeTypeAffinityGroupEntryAndAddToMappingIfValid(entry, mapping);
Assert.assertEquals(1, mapping.size());
Assert.assertEquals(Arrays.asList(1L, 2L), mapping.get("WORKER"));
}
@Test
public void testGetAffinityGroupNodeTypeMapEmptyMap() {
Map<String, List<Long>> result = kubernetesServiceHelper.getAffinityGroupNodeTypeMap(null);
Assert.assertTrue(result.isEmpty());
result = kubernetesServiceHelper.getAffinityGroupNodeTypeMap(new HashMap<>());
Assert.assertTrue(result.isEmpty());
}
@Test
public void testGetAffinityGroupNodeTypeMapValidEntries() {
AffinityGroupVO controlAffinityGroup = Mockito.mock(AffinityGroupVO.class);
Mockito.when(controlAffinityGroup.getId()).thenReturn(100L);
Mockito.when(affinityGroupDao.findByUuid("control-affinity-uuid")).thenReturn(controlAffinityGroup);
AffinityGroupVO workerAffinityGroup = Mockito.mock(AffinityGroupVO.class);
Mockito.when(workerAffinityGroup.getId()).thenReturn(200L);
Mockito.when(affinityGroupDao.findByUuid("worker-affinity-uuid")).thenReturn(workerAffinityGroup);
Map<String, Map<String, String>> affinityGroupNodeTypeMap = new HashMap<>();
Map<String, String> controlEntry = new HashMap<>();
controlEntry.put(VmDetailConstants.CKS_NODE_TYPE, "control");
controlEntry.put(VmDetailConstants.AFFINITY_GROUP, "control-affinity-uuid");
affinityGroupNodeTypeMap.put("0", controlEntry);
Map<String, String> workerEntry = new HashMap<>();
workerEntry.put(VmDetailConstants.CKS_NODE_TYPE, "worker");
workerEntry.put(VmDetailConstants.AFFINITY_GROUP, "worker-affinity-uuid");
affinityGroupNodeTypeMap.put("1", workerEntry);
Map<String, List<Long>> result = kubernetesServiceHelper.getAffinityGroupNodeTypeMap(affinityGroupNodeTypeMap);
Assert.assertEquals(2, result.size());
Assert.assertEquals(Arrays.asList(100L), result.get("CONTROL"));
Assert.assertEquals(Arrays.asList(200L), result.get("WORKER"));
}
@Test
public void testGetAffinityGroupNodeTypeMapMultipleIdsPerNodeType() {
AffinityGroupVO ag1 = Mockito.mock(AffinityGroupVO.class);
AffinityGroupVO ag2 = Mockito.mock(AffinityGroupVO.class);
AffinityGroupVO ag3 = Mockito.mock(AffinityGroupVO.class);
Mockito.when(ag1.getId()).thenReturn(1L);
Mockito.when(ag2.getId()).thenReturn(2L);
Mockito.when(ag3.getId()).thenReturn(3L);
Mockito.when(affinityGroupDao.findByUuid("ag1")).thenReturn(ag1);
Mockito.when(affinityGroupDao.findByUuid("ag2")).thenReturn(ag2);
Mockito.when(affinityGroupDao.findByUuid("ag3")).thenReturn(ag3);
Map<String, Map<String, String>> affinityGroupNodeTypeMap = new HashMap<>();
Map<String, String> controlEntry = new HashMap<>();
controlEntry.put(VmDetailConstants.CKS_NODE_TYPE, "control");
controlEntry.put(VmDetailConstants.AFFINITY_GROUP, "ag1,ag2,ag3");
affinityGroupNodeTypeMap.put("0", controlEntry);
Map<String, List<Long>> result = kubernetesServiceHelper.getAffinityGroupNodeTypeMap(affinityGroupNodeTypeMap);
Assert.assertEquals(1, result.size());
Assert.assertEquals(Arrays.asList(1L, 2L, 3L), result.get("CONTROL"));
}
}

View File

@ -16,8 +16,14 @@
// under the License.
package com.cloud.kubernetes.cluster.actionworkers;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import org.apache.cloudstack.affinity.AffinityGroupVO;
import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
import org.apache.cloudstack.api.ApiConstants;
import org.junit.Assert;
import org.junit.Before;
@ -30,6 +36,8 @@ import org.mockito.junit.MockitoJUnitRunner;
import com.cloud.kubernetes.cluster.KubernetesCluster;
import com.cloud.kubernetes.cluster.KubernetesClusterDetailsVO;
import com.cloud.kubernetes.cluster.KubernetesClusterManagerImpl;
import com.cloud.kubernetes.cluster.KubernetesServiceHelper.KubernetesClusterNodeType;
import com.cloud.kubernetes.cluster.dao.KubernetesClusterAffinityGroupMapDao;
import com.cloud.kubernetes.cluster.dao.KubernetesClusterDao;
import com.cloud.kubernetes.cluster.dao.KubernetesClusterDetailsDao;
import com.cloud.kubernetes.cluster.dao.KubernetesClusterVmMapDao;
@ -60,6 +68,12 @@ public class KubernetesClusterActionWorkerTest {
@Mock
IPAddressDao ipAddressDao;
@Mock
KubernetesClusterAffinityGroupMapDao kubernetesClusterAffinityGroupMapDao;
@Mock
AffinityGroupDao affinityGroupDao;
KubernetesClusterActionWorker actionWorker = null;
final static Long DEFAULT_ID = 1L;
@ -70,10 +84,12 @@ public class KubernetesClusterActionWorkerTest {
kubernetesClusterManager.kubernetesSupportedVersionDao = kubernetesSupportedVersionDao;
kubernetesClusterManager.kubernetesClusterDetailsDao = kubernetesClusterDetailsDao;
kubernetesClusterManager.kubernetesClusterVmMapDao = kubernetesClusterVmMapDao;
kubernetesClusterManager.kubernetesClusterAffinityGroupMapDao = kubernetesClusterAffinityGroupMapDao;
KubernetesCluster kubernetesCluster = Mockito.mock(KubernetesCluster.class);
Mockito.when(kubernetesCluster.getId()).thenReturn(DEFAULT_ID);
actionWorker = new KubernetesClusterActionWorker(kubernetesCluster, kubernetesClusterManager);
actionWorker.ipAddressDao = ipAddressDao;
actionWorker.affinityGroupDao = affinityGroupDao;
}
@Test
@ -130,4 +146,87 @@ public class KubernetesClusterActionWorkerTest {
IpAddress result = actionWorker.getVpcTierKubernetesPublicIp(mockNetworkForGetVpcTierKubernetesPublicIpTest());
Assert.assertNotNull(result);
}
@Test
public void testGetAffinityGroupIdsForNodeTypeReturnsIds() {
Mockito.when(kubernetesClusterAffinityGroupMapDao.listAffinityGroupIdsByClusterIdAndNodeType(DEFAULT_ID, "CONTROL"))
.thenReturn(Arrays.asList(1L, 2L));
List<Long> result = actionWorker.getAffinityGroupIdsForNodeType(KubernetesClusterNodeType.CONTROL);
Assert.assertEquals(2, result.size());
Assert.assertTrue(result.containsAll(Arrays.asList(1L, 2L)));
}
@Test
public void testGetAffinityGroupIdsForNodeTypeReturnsEmptyList() {
Mockito.when(kubernetesClusterAffinityGroupMapDao.listAffinityGroupIdsByClusterIdAndNodeType(DEFAULT_ID, "WORKER"))
.thenReturn(Collections.emptyList());
List<Long> result = actionWorker.getAffinityGroupIdsForNodeType(KubernetesClusterNodeType.WORKER);
Assert.assertTrue(result.isEmpty());
}
@Test
public void testGetMergedAffinityGroupIdsWithExplicitDedication() {
Mockito.when(kubernetesClusterAffinityGroupMapDao.listAffinityGroupIdsByClusterIdAndNodeType(DEFAULT_ID, "CONTROL"))
.thenReturn(new ArrayList<>(Arrays.asList(1L)));
AffinityGroupVO explicitGroup = Mockito.mock(AffinityGroupVO.class);
Mockito.when(explicitGroup.getId()).thenReturn(99L);
Mockito.when(affinityGroupDao.findByAccountAndType(Mockito.anyLong(), Mockito.eq("ExplicitDedication")))
.thenReturn(explicitGroup);
List<Long> result = actionWorker.getMergedAffinityGroupIds(KubernetesClusterNodeType.CONTROL, 1L, 1L);
Assert.assertEquals(2, result.size());
Assert.assertTrue(result.contains(1L));
Assert.assertTrue(result.contains(99L));
}
@Test
public void testGetMergedAffinityGroupIdsNoExplicitDedication() {
Mockito.when(kubernetesClusterAffinityGroupMapDao.listAffinityGroupIdsByClusterIdAndNodeType(DEFAULT_ID, "WORKER"))
.thenReturn(new ArrayList<>(Arrays.asList(1L, 2L)));
Mockito.when(affinityGroupDao.findByAccountAndType(Mockito.anyLong(), Mockito.eq("ExplicitDedication")))
.thenReturn(null);
Mockito.when(affinityGroupDao.findDomainLevelGroupByType(Mockito.anyLong(), Mockito.eq("ExplicitDedication")))
.thenReturn(null);
List<Long> result = actionWorker.getMergedAffinityGroupIds(KubernetesClusterNodeType.WORKER, 1L, 1L);
Assert.assertEquals(2, result.size());
}
@Test
public void testGetMergedAffinityGroupIdsReturnsNullWhenEmpty() {
Mockito.when(kubernetesClusterAffinityGroupMapDao.listAffinityGroupIdsByClusterIdAndNodeType(DEFAULT_ID, "ETCD"))
.thenReturn(new ArrayList<>());
Mockito.when(affinityGroupDao.findByAccountAndType(Mockito.anyLong(), Mockito.anyString()))
.thenReturn(null);
Mockito.when(affinityGroupDao.findDomainLevelGroupByType(Mockito.anyLong(), Mockito.anyString()))
.thenReturn(null);
List<Long> result = actionWorker.getMergedAffinityGroupIds(KubernetesClusterNodeType.ETCD, 1L, 1L);
Assert.assertNull(result);
}
@Test
public void testGetMergedAffinityGroupIdsExplicitDedicationAlreadyInList() {
Mockito.when(kubernetesClusterAffinityGroupMapDao.listAffinityGroupIdsByClusterIdAndNodeType(DEFAULT_ID, "CONTROL"))
.thenReturn(new ArrayList<>(Arrays.asList(99L, 2L)));
AffinityGroupVO explicitGroup = Mockito.mock(AffinityGroupVO.class);
Mockito.when(explicitGroup.getId()).thenReturn(99L);
Mockito.when(affinityGroupDao.findByAccountAndType(Mockito.anyLong(), Mockito.eq("ExplicitDedication")))
.thenReturn(explicitGroup);
List<Long> result = actionWorker.getMergedAffinityGroupIds(KubernetesClusterNodeType.CONTROL, 1L, 1L);
Assert.assertEquals(2, result.size());
Assert.assertTrue(result.contains(99L));
Assert.assertTrue(result.contains(2L));
}
}

View File

@ -25,21 +25,30 @@ import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.api.auth.SetupUserTwoFactorAuthenticationCmd;
import org.apache.cloudstack.acl.RolePermissionEntity;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.command.admin.account.CreateAccountCmd;
import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
import org.apache.cloudstack.api.command.admin.user.DeleteUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.ListUserKeyRulesCmd;
import org.apache.cloudstack.api.command.admin.user.ListUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
import org.apache.cloudstack.api.command.admin.user.RegisterUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
import org.apache.cloudstack.api.response.UserTwoFactorAuthenticationSetupResponse;
import org.apache.cloudstack.auth.UserTwoFactorAuthenticator;
import org.apache.cloudstack.backup.BackupOffering;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.response.ApiKeyPairResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd;
import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
import org.apache.cloudstack.api.command.admin.user.RegisterUserKeyCmd;
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
import org.apache.cloudstack.context.CallContext;
import com.cloud.api.query.vo.ControlledViewEntity;
@ -119,7 +128,7 @@ public class MockAccountManager extends ManagerBase implements AccountManager {
}
@Override
public String[] createApiKeyAndSecretKey(RegisterUserKeyCmd arg0) {
public ApiKeyPair createApiKeyAndSecretKey(RegisterUserKeysCmd arg0) {
// TODO Auto-generated method stub
return null;
}
@ -401,7 +410,7 @@ public class MockAccountManager extends ManagerBase implements AccountManager {
}
@Override
public Pair<User, Account> findUserByApiKey(String arg0) {
public Ternary<User, Account, ApiKeyPair> findUserByApiKey(String arg0) {
// TODO Auto-generated method stub
return null;
}
@ -466,6 +475,10 @@ public class MockAccountManager extends ManagerBase implements AccountManager {
// TODO Auto-generated method stub
}
@Override
public void validateCallingUserHasAccessToDesiredUser(Long userId) {
}
@Override
public Long finalizeAccountId(String accountName, Long domainId, Long projectId, boolean enabledOnly) {
// TODO Auto-generated method stub
@ -503,10 +516,23 @@ public class MockAccountManager extends ManagerBase implements AccountManager {
}
@Override
public Pair<Boolean, Map<String, String>> getKeys(Long userId) {
public ListResponse<ApiKeyPairResponse> listKeys(ListUserKeysCmd cmd) {
return null;
}
@Override
public List<ApiKeyPairPermission> listKeyRules(ListUserKeyRulesCmd cmd) {
return null;
}
@Override
public void deleteApiKey(DeleteUserKeysCmd cmd) {
}
@Override
public void deleteApiKey(ApiKeyPair id) {
}
@Override
public List<UserTwoFactorAuthenticator> listUserTwoFactorAuthenticationProviders() {
return null;
@ -517,6 +543,31 @@ public class MockAccountManager extends ManagerBase implements AccountManager {
return null;
}
@Override
public ApiKeyPair getLatestUserKeyPair(Long userId) {
return null;
}
@Override
public ApiKeyPair getKeyPairById(Long id) {
return null;
}
@Override
public ApiKeyPair getKeyPairByApiKey(String apiKey) {
return null;
}
@Override
public String getAccessingApiKey(BaseCmd cmd) {
return null;
}
@Override
public List<RolePermissionEntity> getAllKeypairPermissions(String apiKey) {
return List.of();
}
@Override
public void checkAccess(User user, ControlledEntity entity)
throws PermissionDeniedException {
@ -538,7 +589,7 @@ public class MockAccountManager extends ManagerBase implements AccountManager {
}
@Override
public void checkApiAccess(Account account, String command) throws PermissionDeniedException {
public void checkApiAccess(Account account, String command, String apiKey) throws PermissionDeniedException {
}
@Override
@ -549,4 +600,12 @@ public class MockAccountManager extends ManagerBase implements AccountManager {
@Override
public void verifyCallerPrivilegeForUserOrAccountOperations(Account userAccount) {
}
@Override
public void verifyCallerPrivilegeForUserOrAccountOperations(User user) {
}
@Override
public void checkCallerRoleTypeAllowedForUserOrAccountOperations(Account userAccount, User user) {
}
}

View File

@ -19,6 +19,7 @@ package org.apache.cloudstack.network.tungsten.service;
import com.cloud.agent.AgentManager;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.Command;
import com.cloud.api.ApiDBUtils;
import com.cloud.configuration.Config;
import com.cloud.configuration.ConfigurationManager;
import com.cloud.dc.DataCenter;
@ -114,6 +115,7 @@ import net.juniper.tungsten.api.types.TagType;
import net.juniper.tungsten.api.types.VirtualMachine;
import net.juniper.tungsten.api.types.VirtualMachineInterface;
import net.juniper.tungsten.api.types.VirtualNetwork;
import org.apache.cloudstack.acl.ApiKeyPairVO;
import org.apache.cloudstack.api.BaseResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
@ -1182,9 +1184,10 @@ public class TungstenServiceImpl extends ManagerBase implements TungstenService
int listenerPort = NetUtils.HTTPS_PORT;
User callerUser = accountMgr.getActiveUser(CallContext.current().getCallingUserId());
String apiKey = callerUser.getApiKey();
String secretKey = callerUser.getSecretKey();
if (apiKey != null && secretKey != null) {
ApiKeyPairVO latestKeypair = ApiDBUtils.searchForLatestUserKeyPair(callerUser.getId());
if (latestKeypair != null) {
String apiKey = latestKeypair.getApiKey();
String secretKey = latestKeypair.getSecretKey();
String url;
try {
String data = "apiKey=" + URLEncoder.encode(apiKey, StandardCharsets.UTF_8.name()).replace("\\+", "%20") + "&command"

View File

@ -16,6 +16,7 @@
// under the License.
package org.apache.cloudstack.network.tungsten.service;
import org.apache.cloudstack.acl.ApiKeyPairVO;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@ -360,6 +361,10 @@ public class TungstenElementTest {
when(lbStickinessPolicy.getMethodName()).thenReturn("AppCookie");
List<Pair<String, String>> pairList = List.of(new Pair<>("cookieName", "cookieValue"));
ApiKeyPairVO latest = new ApiKeyPairVO();
latest.setApiKey("apikey");
latest.setSecretKey("secretkey");
when(ApiDBUtils.searchForLatestUserKeyPair(Mockito.anyLong())).thenReturn(latest);
when(lbStickinessPolicy.getParams()).thenReturn(pairList);
when(loadBalancingRule1.getId()).thenReturn(1L);
when(loadBalancingRule1.getState()).thenReturn(FirewallRule.State.Add);

View File

@ -55,7 +55,7 @@ public class S3ImageStoreDriverImpl extends BaseImageStoreDriverImpl {
return new S3TO(imgStore.getId(),
imgStore.getUuid(),
details.get(ApiConstants.S3_ACCESS_KEY),
details.get(ApiConstants.S3_SECRET_KEY),
details.get(ApiConstants.SECRET_KEY),
details.get(ApiConstants.S3_END_POINT),
details.get(ApiConstants.S3_BUCKET_NAME),
details.get(ApiConstants.S3_SIGNER),

Some files were not shown because too many files have changed in this diff Show More