2626import java .util .HashMap ;
2727import java .util .List ;
2828import java .util .Map ;
29+ import java .util .UUID ;
2930import java .util .concurrent .ConcurrentHashMap ;
3031import java .util .concurrent .Executors ;
3132import java .util .concurrent .ScheduledExecutorService ;
3233import java .util .concurrent .TimeUnit ;
3334import java .util .regex .Matcher ;
3435import java .util .regex .Pattern ;
36+ import java .util .stream .Collectors ;
3537
3638import javax .inject .Inject ;
3739import javax .naming .ConfigurationException ;
3840
3941import org .apache .cloudstack .acl .ControlledEntity ;
42+ import org .apache .cloudstack .acl .Role ;
43+ import org .apache .cloudstack .acl .RoleService ;
44+ import org .apache .cloudstack .acl .RoleType ;
45+ import org .apache .cloudstack .acl .Rule ;
4046import org .apache .cloudstack .acl .SecurityChecker ;
47+ import org .apache .cloudstack .acl .RolePermissionEntity .Permission ;
4148import org .apache .cloudstack .api .ApiConstants ;
4249import org .apache .cloudstack .api .ApiConstants .VMDetails ;
4350import org .apache .cloudstack .api .ResponseObject .ResponseView ;
5360import org .apache .cloudstack .api .response .KubernetesClusterResponse ;
5461import org .apache .cloudstack .api .response .ListResponse ;
5562import org .apache .cloudstack .api .response .UserVmResponse ;
63+ import org .apache .cloudstack .config .ApiServiceConfiguration ;
5664import org .apache .cloudstack .context .CallContext ;
5765import org .apache .cloudstack .engine .orchestration .service .NetworkOrchestrationService ;
5866import org .apache .cloudstack .framework .config .ConfigKey ;
131139import com .cloud .user .Account ;
132140import com .cloud .user .AccountManager ;
133141import com .cloud .user .AccountService ;
142+ import com .cloud .user .AccountVO ;
134143import com .cloud .user .SSHKeyPairVO ;
144+ import com .cloud .user .User ;
145+ import com .cloud .user .UserAccount ;
146+ import com .cloud .user .dao .AccountDao ;
135147import com .cloud .user .dao .SSHKeyPairDao ;
136148import com .cloud .utils .Pair ;
137149import com .cloud .utils .Ternary ;
@@ -167,6 +179,8 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
167179 ScheduledExecutorService _gcExecutor ;
168180 ScheduledExecutorService _stateScanner ;
169181
182+ Account kubeadmin ;
183+
170184 @ Inject
171185 public KubernetesClusterDao kubernetesClusterDao ;
172186 @ Inject
@@ -192,6 +206,10 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
192206 @ Inject
193207 protected TemplateJoinDao templateJoinDao ;
194208 @ Inject
209+ protected AccountDao accountDao ;
210+ @ Inject
211+ protected RoleService roleService ;
212+ @ Inject
195213 protected AccountService accountService ;
196214 @ Inject
197215 protected AccountManager accountManager ;
@@ -222,6 +240,45 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
222240 @ Inject
223241 protected FirewallRulesDao firewallRulesDao ;
224242
243+ private Role createKubeadminRole () {
244+ Role kubeadminRole = roleService .createRole (KUBEADMIN_ACCOUNT_NAME , RoleType .Admin , "Default Kubeadmin role" );
245+ roleService .createRolePermission (kubeadminRole , new Rule ("listKubernetesClusters" ), Permission .ALLOW , "" );
246+ roleService .createRolePermission (kubeadminRole , new Rule ("scaleKubernetesCluster" ), Permission .ALLOW , "" );
247+ roleService .createRolePermission (kubeadminRole , new Rule ("*" ), Permission .DENY , "" );
248+ return kubeadminRole ;
249+ }
250+
251+ private void init () {
252+
253+ // Check and create Kubeadmin role
254+ Role kubeadminRole = null ;
255+ List <Role > roles = roleService .findRolesByName (KUBEADMIN_ACCOUNT_NAME );
256+ if (roles == null ) {
257+ kubeadminRole = createKubeadminRole ();
258+ } else {
259+ roles = roles .stream ().filter (x -> x .getDescription () == "Default Kubeadmin role" ).collect (Collectors .toList ());
260+ if (roles .size () != 1 ) {
261+ kubeadminRole = createKubeadminRole ();
262+ } else {
263+ kubeadminRole = roles .get (0 );
264+ }
265+ }
266+
267+ // Check and create Kubeadmin account
268+ if (accountManager .getActiveAccountByName (KUBEADMIN_ACCOUNT_NAME , 1L ) != null ) {
269+ return ;
270+ }
271+
272+ AccountVO kubeadmin = new AccountVO ();
273+ kubeadmin .setAccountName (KUBEADMIN_ACCOUNT_NAME );
274+ kubeadmin .setUuid (UUID .randomUUID ().toString ());
275+ kubeadmin .setState (Account .State .enabled );
276+ kubeadmin .setDomainId (1 );
277+ kubeadmin .setType (RoleType .Admin .getAccountType ());
278+ kubeadmin .setRoleId (kubeadminRole .getId ());
279+ kubeadmin = accountDao .persist (kubeadmin );
280+ }
281+
225282 private void logMessage (final Level logLevel , final String message , final Exception e ) {
226283 if (logLevel == Level .WARN ) {
227284 if (e != null ) {
@@ -628,6 +685,13 @@ public KubernetesClusterResponse createKubernetesClusterResponse(long kubernetes
628685 }
629686 }
630687 response .setVirtualMachineIds (vmIds );
688+ Boolean isAutoscalingEnabled = kubernetesCluster .isAutoscalingEnabled ();
689+ LOGGER .warn ("Autoscaling enabled : " + isAutoscalingEnabled );
690+ if (isAutoscalingEnabled != null ) {
691+ response .setAutoscalingEnabled (isAutoscalingEnabled );
692+ }
693+ response .setMinSize (kubernetesCluster .getMinSize ());
694+ response .setMaxSize (kubernetesCluster .getMaxSize ());
631695 return response ;
632696 }
633697
@@ -834,17 +898,59 @@ private void validateKubernetesClusterScaleParameters(ScaleKubernetesClusterCmd
834898 final Long serviceOfferingId = cmd .getServiceOfferingId ();
835899 final Long clusterSize = cmd .getClusterSize ();
836900 final List <Long > nodeIds = cmd .getNodeIds ();
837- if (kubernetesClusterId == null || kubernetesClusterId < 1L ) {
838- throw new InvalidParameterValueException ("Invalid Kubernetes cluster ID" );
839- }
901+ final Boolean isAutoscalingEnabled = cmd .isAutoscalingEnabled ();
902+ final Long minSize = cmd .getMinSize ();
903+ final Long maxSize = cmd .getMaxSize ();
904+
840905 KubernetesClusterVO kubernetesCluster = kubernetesClusterDao .findById (kubernetesClusterId );
841906 if (kubernetesCluster == null || kubernetesCluster .getRemoved () != null ) {
842907 throw new InvalidParameterValueException ("Invalid Kubernetes cluster ID" );
843908 }
909+
844910 final DataCenter zone = dataCenterDao .findById (kubernetesCluster .getZoneId ());
845911 if (zone == null ) {
846912 logAndThrow (Level .WARN , String .format ("Unable to find zone for Kubernetes cluster ID: %s" , kubernetesCluster .getUuid ()));
847913 }
914+
915+ if (serviceOfferingId == null && clusterSize == null && nodeIds == null && isAutoscalingEnabled == null ) {
916+ throw new InvalidParameterValueException (String .format ("Kubernetes cluster ID: %s cannot be scaled, either a new service offering or a new cluster size or nodeids to be removed or autoscaling must be passed" , kubernetesCluster .getUuid ()));
917+ }
918+
919+ Account caller = CallContext .current ().getCallingAccount ();
920+ accountManager .checkAccess (caller , SecurityChecker .AccessType .OperateEntry , false , kubernetesCluster );
921+
922+ final KubernetesSupportedVersion clusterVersion = kubernetesSupportedVersionDao .findById (kubernetesCluster .getKubernetesVersionId ());
923+ if (clusterVersion == null ) {
924+ throw new CloudRuntimeException (String .format ("Invalid Kubernetes version associated with Kubernetes cluster ID: %s" , kubernetesCluster .getUuid ()));
925+ }
926+
927+ if (!(kubernetesCluster .getState ().equals (KubernetesCluster .State .Created ) ||
928+ kubernetesCluster .getState ().equals (KubernetesCluster .State .Running ) ||
929+ kubernetesCluster .getState ().equals (KubernetesCluster .State .Stopped ))) {
930+ throw new PermissionDeniedException (String .format ("Kubernetes cluster ID: %s is in %s state" , kubernetesCluster .getUuid (), kubernetesCluster .getState ().toString ()));
931+ }
932+
933+ if (isAutoscalingEnabled != null && isAutoscalingEnabled ) {
934+ if (clusterSize != null || serviceOfferingId != null || nodeIds != null ) {
935+ throw new InvalidParameterValueException ("autoscaling can not be passed along with nodeids or clustersize or service offering" );
936+ }
937+
938+ String csUrl = ApiServiceConfiguration .ApiServletPath .value ();
939+ if (csUrl == null || csUrl .contains ("localhost" )) {
940+ throw new InvalidParameterValueException ("Global setting endpointe.url has to be set to the Management Server's API end point" );
941+ }
942+
943+ if (minSize == null || maxSize == null ) {
944+ throw new InvalidParameterValueException ("autoscaling requires minsize and maxsize to be passed" );
945+ }
946+ if (minSize < 1 ) {
947+ throw new InvalidParameterValueException ("minsize must be more than 1" );
948+ }
949+ if (maxSize <= minSize ) {
950+ throw new InvalidParameterValueException ("maxsize must be greater than minsize" );
951+ }
952+ }
953+
848954 if (nodeIds != null ) {
849955 if (clusterSize != null || serviceOfferingId != null ) {
850956 throw new InvalidParameterValueException ("nodeids can not be passed along with clustersize or service offering" );
@@ -859,19 +965,6 @@ private void validateKubernetesClusterScaleParameters(ScaleKubernetesClusterCmd
859965 if (mastersToRemove >= kubernetesCluster .getMasterNodeCount ()) {
860966 throw new InvalidParameterValueException ("Can not remove all masters from a cluster" );
861967 }
862-
863- } else {
864- if (serviceOfferingId == null && clusterSize == null ) {
865- throw new InvalidParameterValueException (String .format ("Kubernetes cluster ID: %s cannot be scaled, either a new service offering or a new cluster size or nodeids to be removed must be passed" , kubernetesCluster .getUuid ()));
866- }
867- }
868-
869- Account caller = CallContext .current ().getCallingAccount ();
870- accountManager .checkAccess (caller , SecurityChecker .AccessType .OperateEntry , false , kubernetesCluster );
871-
872- final KubernetesSupportedVersion clusterVersion = kubernetesSupportedVersionDao .findById (kubernetesCluster .getKubernetesVersionId ());
873- if (clusterVersion == null ) {
874- throw new CloudRuntimeException (String .format ("Invalid Kubernetes version associated with Kubernetes cluster ID: %s" , kubernetesCluster .getUuid ()));
875968 }
876969
877970 ServiceOffering serviceOffering = null ;
@@ -904,12 +997,6 @@ private void validateKubernetesClusterScaleParameters(ScaleKubernetesClusterCmd
904997 }
905998 }
906999
907- if (!(kubernetesCluster .getState ().equals (KubernetesCluster .State .Created ) ||
908- kubernetesCluster .getState ().equals (KubernetesCluster .State .Running ) ||
909- kubernetesCluster .getState ().equals (KubernetesCluster .State .Stopped ))) {
910- throw new PermissionDeniedException (String .format ("Kubernetes cluster ID: %s is in %s state" , kubernetesCluster .getUuid (), kubernetesCluster .getState ().toString ()));
911- }
912-
9131000 if (clusterSize != null ) {
9141001 if (kubernetesCluster .getState ().equals (KubernetesCluster .State .Stopped )) { // Cannot scale stopped cluster currently for cluster size
9151002 throw new PermissionDeniedException (String .format ("Kubernetes cluster ID: %s is in %s state" , kubernetesCluster .getUuid (), kubernetesCluster .getState ().toString ()));
@@ -1226,16 +1313,50 @@ public KubernetesClusterConfigResponse getKubernetesClusterConfig(GetKubernetesC
12261313 return response ;
12271314 }
12281315
1316+ private String [] createServiceAccount () {
1317+ // TODO : Maybe create a service account kubeadmin with restricted permissions and add the user to that instead
1318+ Account caller = CallContext .current ().getCallingAccount ();
1319+ String username = caller .getAccountName () + "-kubeadmin" ;
1320+ UserAccount kubeadmin = accountService .getActiveUserAccount (username , caller .getDomainId ());
1321+ String [] keys = null ;
1322+ if (kubeadmin == null ) {
1323+ User kube = accountService .createUser (username , "password" , "kube" , "admin" , "kubeadmin" , null , caller .getAccountName (), caller .getDomainId (), null );
1324+ keys = accountService .createApiKeyAndSecretKey (kube .getId ());
1325+ } else {
1326+ String apiKey = kubeadmin .getApiKey ();
1327+ String secretKey = kubeadmin .getSecretKey ();
1328+ if (Strings .isNullOrEmpty (apiKey ) || Strings .isNullOrEmpty (secretKey )) {
1329+ keys = accountService .createApiKeyAndSecretKey (kubeadmin .getId ());
1330+ } else {
1331+ keys = new String []{apiKey , secretKey };
1332+ }
1333+ }
1334+ return keys ;
1335+ }
1336+
12291337 @ Override
12301338 public boolean scaleKubernetesCluster (ScaleKubernetesClusterCmd cmd ) throws CloudRuntimeException {
12311339 if (!KubernetesServiceEnabled .value ()) {
12321340 logAndThrow (Level .ERROR , "Kubernetes Service plugin is disabled" );
12331341 }
12341342 validateKubernetesClusterScaleParameters (cmd );
1343+
1344+ Boolean isAutoscalingEnabled = cmd .isAutoscalingEnabled ();
1345+ String [] keys = null ;
1346+ if (isAutoscalingEnabled != null && isAutoscalingEnabled ) {
1347+ keys = createServiceAccount ();
1348+ }
1349+
12351350 KubernetesClusterScaleWorker scaleWorker =
12361351 new KubernetesClusterScaleWorker (kubernetesClusterDao .findById (cmd .getId ()),
1237- serviceOfferingDao .findById (cmd .getServiceOfferingId ()), cmd .getClusterSize (),
1238- cmd .getNodeIds (), this );
1352+ serviceOfferingDao .findById (cmd .getServiceOfferingId ()),
1353+ cmd .getClusterSize (),
1354+ cmd .getNodeIds (),
1355+ cmd .isAutoscalingEnabled (),
1356+ cmd .getMinSize (),
1357+ cmd .getMaxSize (),
1358+ keys ,
1359+ this );
12391360 scaleWorker = ComponentContext .inject (scaleWorker );
12401361 return scaleWorker .scaleCluster ();
12411362 }
0 commit comments