mirror of https://github.com/apache/jclouds.git
adminOrg base continued- media types, restClient config, and checks
This commit is contained in:
parent
1f3971fae8
commit
1df9409a3a
|
@ -135,6 +135,8 @@ public class VCloudDirectorMediaType {
|
||||||
|
|
||||||
public static final String GROUP = "application/vnd.vmware.admin.group+xml";
|
public static final String GROUP = "application/vnd.vmware.admin.group+xml";
|
||||||
|
|
||||||
|
public static final String ORG_VAPP_TEMPLATE_LEASE_SETTINGS = "application/vnd.vmware.admin.vAppTemplateLeaseSettings+xml";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* All acceptable media types.
|
* All acceptable media types.
|
||||||
|
@ -153,6 +155,8 @@ public class VCloudDirectorMediaType {
|
||||||
CONTROL_ACCESS, VAPP_TEMPLATE, CUSTOMIZATION_SECTION, GUEST_CUSTOMIZATION_SECTION,
|
CONTROL_ACCESS, VAPP_TEMPLATE, CUSTOMIZATION_SECTION, GUEST_CUSTOMIZATION_SECTION,
|
||||||
NETWORK_SECTION, NETWORK_CONFIG_SECTION, NETWORK_CONNECTION_SECTION,
|
NETWORK_SECTION, NETWORK_CONFIG_SECTION, NETWORK_CONNECTION_SECTION,
|
||||||
CLONE_MEDIA_PARAMS, LEASE_SETTINGS_SECTION, RELOCATE_TEMPLATE, ENVELOPE,
|
CLONE_MEDIA_PARAMS, LEASE_SETTINGS_SECTION, RELOCATE_TEMPLATE, ENVELOPE,
|
||||||
PUBLISH_CATALOG_PARAMS, GROUP
|
PUBLISH_CATALOG_PARAMS, GROUP, ORG_VAPP_TEMPLATE_LEASE_SETTINGS
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,6 +109,7 @@ public class VCloudDirectorRestClientModule extends RestClientModule<VCloudDirec
|
||||||
.put(AdminCatalogClient.class, AdminCatalogAsyncClient.class)
|
.put(AdminCatalogClient.class, AdminCatalogAsyncClient.class)
|
||||||
.put(AdminOrgClient.class, AdminOrgAsyncClient.class)
|
.put(AdminOrgClient.class, AdminOrgAsyncClient.class)
|
||||||
.put(GroupClient.class, GroupAsyncClient.class)
|
.put(GroupClient.class, GroupAsyncClient.class)
|
||||||
|
.put(AdminOrgClient.class, AdminOrgAsyncClient.class)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public VCloudDirectorRestClientModule() {
|
public VCloudDirectorRestClientModule() {
|
||||||
|
|
|
@ -21,6 +21,9 @@ package org.jclouds.vcloud.director.v1_5.domain;
|
||||||
|
|
||||||
import static com.google.common.base.Objects.equal;
|
import static com.google.common.base.Objects.equal;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
import javax.xml.bind.annotation.XmlElement;
|
import javax.xml.bind.annotation.XmlElement;
|
||||||
|
@ -86,6 +89,36 @@ import com.google.common.base.Objects.ToStringHelper;
|
||||||
"groupAttributes"
|
"groupAttributes"
|
||||||
})
|
})
|
||||||
public class CustomOrgLdapSettings {
|
public class CustomOrgLdapSettings {
|
||||||
|
public static final class AuthenticationMechanism {
|
||||||
|
public static final String SIMPLE = "simple";
|
||||||
|
public static final String KERBEROS = "kerberos";
|
||||||
|
public static final String MD5DIGEST = "md5digest";
|
||||||
|
public static final String NTLM = "ntlm";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All acceptable {@link CustomOrgLdapSettings#getAuthenticationMechanism()} values.
|
||||||
|
* <p/>
|
||||||
|
* This list must be updated whenever a new authentication mechanism is added.
|
||||||
|
*/
|
||||||
|
public static final List<String> ALL = Arrays.asList(
|
||||||
|
SIMPLE, KERBEROS, MD5DIGEST, NTLM
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class ConnectorType {
|
||||||
|
public static final String ACTIVE_DIRECTORY = "ACTIVE_DIRECTORY";
|
||||||
|
public static final String OPEN_LDAP = "OPEN_LDAP";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All acceptable {@link OrgLdapSettings#getOrgLdapMode()} values.
|
||||||
|
* <p/>
|
||||||
|
* This list must be updated whenever a new mode is added.
|
||||||
|
*/
|
||||||
|
public static final List<String> ALL = Arrays.asList(
|
||||||
|
ACTIVE_DIRECTORY, OPEN_LDAP
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public static Builder<?> builder() {
|
public static Builder<?> builder() {
|
||||||
return new ConcreteBuilder();
|
return new ConcreteBuilder();
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,8 @@ package org.jclouds.vcloud.director.v1_5.domain;
|
||||||
import static com.google.common.base.Objects.equal;
|
import static com.google.common.base.Objects.equal;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
|
@ -66,6 +68,21 @@ import com.google.common.base.Objects.ToStringHelper;
|
||||||
"customOrgLdapSettings"
|
"customOrgLdapSettings"
|
||||||
})
|
})
|
||||||
public class OrgLdapSettings extends ResourceType<OrgLdapSettings> {
|
public class OrgLdapSettings extends ResourceType<OrgLdapSettings> {
|
||||||
|
public static final class LdapMode {
|
||||||
|
public static final String NONE = "none";
|
||||||
|
public static final String SYSTEM = "system";
|
||||||
|
public static final String CUSTOM = "custom";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All acceptable {@link OrgLdapSettings#getOrgLdapMode()} values.
|
||||||
|
* <p/>
|
||||||
|
* This list must be updated whenever a new mode is added.
|
||||||
|
*/
|
||||||
|
public static final List<String> ALL = Arrays.asList(
|
||||||
|
NONE, SYSTEM, CUSTOM
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public static Builder builder() {
|
public static Builder builder() {
|
||||||
return new Builder();
|
return new Builder();
|
||||||
}
|
}
|
||||||
|
|
|
@ -193,7 +193,7 @@ public class OrgPasswordPolicySettings extends ResourceType<OrgPasswordPolicySet
|
||||||
* Gets the value of the invalidLoginsBeforeLockout property.
|
* Gets the value of the invalidLoginsBeforeLockout property.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public int getInvalidLoginsBeforeLockout() {
|
public Integer getInvalidLoginsBeforeLockout() {
|
||||||
return invalidLoginsBeforeLockout;
|
return invalidLoginsBeforeLockout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,7 +201,7 @@ public class OrgPasswordPolicySettings extends ResourceType<OrgPasswordPolicySet
|
||||||
* Gets the value of the accountLockoutIntervalMinutes property.
|
* Gets the value of the accountLockoutIntervalMinutes property.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public int getAccountLockoutIntervalMinutes() {
|
public Integer getAccountLockoutIntervalMinutes() {
|
||||||
return accountLockoutIntervalMinutes;
|
return accountLockoutIntervalMinutes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -111,4 +111,11 @@ public class Reference extends ReferenceType<Reference> {
|
||||||
Reference that = Reference.class.cast(o);
|
Reference that = Reference.class.cast(o);
|
||||||
return super.equals(that);
|
return super.equals(that);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ReferenceType<?> toAdminReference(String endpoint) {
|
||||||
|
return toBuilder()
|
||||||
|
.type(null)
|
||||||
|
.href(URI.create(getHref().toASCIIString().replace(endpoint, endpoint+"/admin")))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -38,6 +38,9 @@ import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.jclouds.vcloud.director.v1_5.VCloudDirectorMediaType;
|
import org.jclouds.vcloud.director.v1_5.VCloudDirectorMediaType;
|
||||||
|
import org.jclouds.vcloud.director.v1_5.domain.CustomOrgLdapSettings.AuthenticationMechanism;
|
||||||
|
import org.jclouds.vcloud.director.v1_5.domain.CustomOrgLdapSettings.ConnectorType;
|
||||||
|
import org.jclouds.vcloud.director.v1_5.domain.OrgLdapSettings.LdapMode;
|
||||||
|
|
||||||
import com.google.common.base.Splitter;
|
import com.google.common.base.Splitter;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
@ -524,4 +527,186 @@ public class Checks {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void checkOrgSetting(OrgSettings settings) {
|
||||||
|
// Check optional fields
|
||||||
|
if (settings.getOrgGeneralSettings() != null) {
|
||||||
|
checkOrgGeneralSettings(settings.getOrgGeneralSettings());
|
||||||
|
}
|
||||||
|
if (settings.getVAppLeaseSettings() != null) {
|
||||||
|
checkVAppLeaseSettings(settings.getVAppLeaseSettings());
|
||||||
|
}
|
||||||
|
if (settings.getVAppTemplateLeaseSettings() != null) {
|
||||||
|
checkVAppTemplateLeaseSettings(settings.getVAppTemplateLeaseSettings());
|
||||||
|
}
|
||||||
|
if (settings.getOrgLdapSettings() != null) {
|
||||||
|
checkLdapSettings(settings.getOrgLdapSettings());
|
||||||
|
}
|
||||||
|
if (settings.getOrgEmailSettings() != null) {
|
||||||
|
checkEmailSettings(settings.getOrgEmailSettings());
|
||||||
|
}
|
||||||
|
if (settings.getOrgPasswordPolicySettings() != null) {
|
||||||
|
checkPasswordPolicySettings(settings.getOrgPasswordPolicySettings());
|
||||||
|
}
|
||||||
|
|
||||||
|
// parent type
|
||||||
|
checkResourceType(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void checkEmailSettings(OrgEmailSettings settings) {
|
||||||
|
// required
|
||||||
|
assertNotNull(settings.isDefaultSmtpServer(), String.format(OBJ_FIELD_REQ, "OrgEmailSettings", "isDefaultSmtpServer"));
|
||||||
|
assertNotNull(settings.isDefaultOrgEmail(), String.format(OBJ_FIELD_REQ, "OrgEmailSettings", "isDefaultOrgEmail"));
|
||||||
|
assertNotNull(settings.getFromEmailAddress(), String.format(OBJ_FIELD_REQ, "OrgEmailSettings", "fromEmailAddress"));
|
||||||
|
checkEmailAddress(settings.getFromEmailAddress());
|
||||||
|
assertNotNull(settings.getDefaultSubjectPrefix(), String.format(OBJ_FIELD_REQ, "OrgEmailSettings", "defaultSubjectPrefix"));
|
||||||
|
assertNotNull(settings.isAlertEmailToAllAdmins(), String.format(OBJ_FIELD_REQ, "OrgEmailSettings", "isAlertEmailToAllAdmins"));
|
||||||
|
assertNotNull(settings.getAlertEmailsTo(), String.format(OBJ_FIELD_REQ, "OrgEmailSettings", "alertEmailsTo"));
|
||||||
|
|
||||||
|
// optional
|
||||||
|
// NOTE alertEmailsTo cannot be checked
|
||||||
|
|
||||||
|
// parent type
|
||||||
|
checkResourceType(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void checkEmailAddress(String email) {
|
||||||
|
// TODO: validate email addresses
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void checkOrgGeneralSettings(OrgGeneralSettings settings) {
|
||||||
|
// Check optional fields
|
||||||
|
// NOTE canPublishCatalogs cannot be checked
|
||||||
|
// NOTE useServerBootSequence cannot be checked
|
||||||
|
if (settings.getDeployedVMQuota() != null) {
|
||||||
|
assertTrue(settings.getDeployedVMQuota() >= 0, String.format(
|
||||||
|
OBJ_FIELD_GTE_0, "deployedVMQuota", "port", settings.getDeployedVMQuota()));
|
||||||
|
}
|
||||||
|
if (settings.getStoredVmQuota() != null) {
|
||||||
|
assertTrue(settings.getStoredVmQuota() >= 0, String.format(
|
||||||
|
OBJ_FIELD_GTE_0, "storedVmQuota", "port", settings.getStoredVmQuota()));
|
||||||
|
}
|
||||||
|
if (settings.getDelayAfterPowerOnSeconds() != null) {
|
||||||
|
assertTrue(settings.getDelayAfterPowerOnSeconds() >= 0, String.format(
|
||||||
|
OBJ_FIELD_GTE_0, "delayAfterPowerOnSeconds", "port", settings.getDelayAfterPowerOnSeconds()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// parent type
|
||||||
|
checkResourceType(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void checkLdapSettings(OrgLdapSettings settings) {
|
||||||
|
// Check optional fields
|
||||||
|
// NOTE customUsersOu cannot be checked
|
||||||
|
if (settings.getOrgLdapMode() != null) {
|
||||||
|
assertTrue(LdapMode.ALL.contains(settings.getOrgLdapMode()), String.format(REQUIRED_VALUE_OBJECT_FMT,
|
||||||
|
"LdapMode", "OrdLdapSettings", settings.getOrgLdapMode(), Iterables.toString(OrgLdapSettings.LdapMode.ALL)));
|
||||||
|
}
|
||||||
|
if (settings.getCustomOrgLdapSettings() != null) {
|
||||||
|
checkCustomOrgLdapSettings(settings.getCustomOrgLdapSettings());
|
||||||
|
}
|
||||||
|
|
||||||
|
// parent type
|
||||||
|
checkResourceType(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void checkCustomOrgLdapSettings(CustomOrgLdapSettings settings) {
|
||||||
|
// required
|
||||||
|
assertNotNull(settings.getHostName(), String.format(OBJ_FIELD_REQ, "CustomOrgLdapSettings", "hostName"));
|
||||||
|
assertNotNull(settings.getPort(), String.format(OBJ_FIELD_REQ, "CustomOrgLdapSettings", "port"));
|
||||||
|
assertTrue(settings.getPort() >= 0, String.format(
|
||||||
|
OBJ_FIELD_GTE_0, "CustomOrgLdapSettings", "port", settings.getPort()));
|
||||||
|
assertNotNull(settings.getAuthenticationMechanism(), String.format(OBJ_FIELD_REQ, "CustomOrgLdapSettings", "authenticationMechanism"));
|
||||||
|
assertTrue(AuthenticationMechanism.ALL.contains(settings.getAuthenticationMechanism()), String.format(REQUIRED_VALUE_OBJECT_FMT,
|
||||||
|
"AuthenticationMechanism", "CustomOrdLdapSettings", settings.getAuthenticationMechanism(),
|
||||||
|
Iterables.toString(CustomOrgLdapSettings.AuthenticationMechanism.ALL)));
|
||||||
|
assertNotNull(settings.isGroupSearchBaseEnabled(), String.format(OBJ_FIELD_REQ, "CustomOrgLdapSettings", "isGroupSearchBaseEnabled"));
|
||||||
|
assertNotNull(settings.getConnectorType(), String.format(OBJ_FIELD_REQ, "CustomOrgLdapSettings", "connectorType"));
|
||||||
|
assertTrue(ConnectorType.ALL.contains(settings.getConnectorType()), String.format(REQUIRED_VALUE_OBJECT_FMT,
|
||||||
|
"ConnectorType", "CustomOrdLdapSettings", settings.getConnectorType(),
|
||||||
|
Iterables.toString(CustomOrgLdapSettings.ConnectorType.ALL)));
|
||||||
|
assertNotNull(settings.getUserAttributes(), String.format(OBJ_FIELD_REQ, "CustomOrgLdapSettings", "userAttributes"));
|
||||||
|
checkUserAttributes("CustomOrdLdapSettings", settings.getUserAttributes());
|
||||||
|
assertNotNull(settings.getGroupAttributes(), String.format(OBJ_FIELD_REQ, "CustomOrgLdapSettings", "groupAttributes"));
|
||||||
|
checkGroupAttributes("CustomOrdLdapSettings", settings.getGroupAttributes());
|
||||||
|
|
||||||
|
// optional
|
||||||
|
// NOTE isSsl cannot be checked
|
||||||
|
// NOTE isSSlAcceptAll cannot be checked
|
||||||
|
// NOTE realm cannot be checked
|
||||||
|
// NOTE searchBase cannot be checked
|
||||||
|
// NOTE userName cannot be checked
|
||||||
|
// NOTE password cannot be checked
|
||||||
|
// NOTE groupSearchBase cannot be checked
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void checkUserAttributes(String client, OrgLdapUserAttributes attributes) {
|
||||||
|
// required
|
||||||
|
assertNotNull(attributes.getObjectClass(), String.format(OBJ_FIELD_REQ, client, "objectClass"));
|
||||||
|
assertNotNull(attributes.getObjectIdentifier(), String.format(OBJ_FIELD_REQ, client, "objectIdentifier"));
|
||||||
|
assertNotNull(attributes.getUserName(), String.format(OBJ_FIELD_REQ, client, "userName"));
|
||||||
|
assertNotNull(attributes.getEmail(), String.format(OBJ_FIELD_REQ, client, "email"));
|
||||||
|
assertNotNull(attributes.getFullName(), String.format(OBJ_FIELD_REQ, client, "fullName"));
|
||||||
|
assertNotNull(attributes.getGivenName(), String.format(OBJ_FIELD_REQ, client, "givenName"));
|
||||||
|
assertNotNull(attributes.getSurname(), String.format(OBJ_FIELD_REQ, client, "surname"));
|
||||||
|
assertNotNull(attributes.getTelephone(), String.format(OBJ_FIELD_REQ, client, "telephone"));
|
||||||
|
assertNotNull(attributes.getGroupMembershipIdentifier(), String.format(OBJ_FIELD_REQ, client, "groupMembershipIdentifier"));
|
||||||
|
|
||||||
|
// optional
|
||||||
|
// NOTE groupBackLinkIdentifier cannot be checked
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void checkGroupAttributes(String client, OrgLdapGroupAttributes attributes) {
|
||||||
|
// required
|
||||||
|
assertNotNull(attributes.getObjectClass(), String.format(OBJ_FIELD_REQ, client, "objectClass"));
|
||||||
|
assertNotNull(attributes.getObjectIdentifier(), String.format(OBJ_FIELD_REQ, client, "objectIdentifier"));
|
||||||
|
assertNotNull(attributes.getGroupName(), String.format(OBJ_FIELD_REQ, client, "groupName"));
|
||||||
|
assertNotNull(attributes.getMembership(), String.format(OBJ_FIELD_REQ, client, "membership"));
|
||||||
|
assertNotNull(attributes.getMembershipIdentifier(), String.format(OBJ_FIELD_REQ, client, "membershipIdentifier"));
|
||||||
|
|
||||||
|
// optional
|
||||||
|
// NOTE backLinkIdentifier cannot be checked
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void checkPasswordPolicySettings(OrgPasswordPolicySettings settings) {
|
||||||
|
// required
|
||||||
|
assertNotNull(settings.isAccountLockoutEnabled(), String.format(OBJ_FIELD_REQ, "OrgPasswordPolicySettings", "isAccountLockoutEnabled"));
|
||||||
|
assertNotNull(settings.getInvalidLoginsBeforeLockout(), String.format(OBJ_FIELD_REQ, "OrgPasswordPolicySettings", "invalidLoginsBeforeLockout"));
|
||||||
|
assertTrue(settings.getInvalidLoginsBeforeLockout() >= 0, String.format(
|
||||||
|
OBJ_FIELD_GTE_0, "OrgPasswordPolicySettings", "storageLeaseSeconds", settings.getInvalidLoginsBeforeLockout()));
|
||||||
|
assertNotNull(settings.getAccountLockoutIntervalMinutes(), String.format(OBJ_FIELD_REQ, "OrgPasswordPolicySettings", "accountLockoutIntervalMinutes"));
|
||||||
|
assertTrue(settings.getAccountLockoutIntervalMinutes() >= 0, String.format(
|
||||||
|
OBJ_FIELD_GTE_0, "OrgPasswordPolicySettings", "accountLockoutIntervalMinutes", settings.getAccountLockoutIntervalMinutes()));
|
||||||
|
|
||||||
|
// parent type
|
||||||
|
checkResourceType(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void checkVAppLeaseSettings(OrgLeaseSettings settings) {
|
||||||
|
// Check optional fields
|
||||||
|
// NOTE deleteOnStorageLeaseExpiration cannot be checked
|
||||||
|
if (settings.getStorageLeaseSeconds() != null) {
|
||||||
|
assertTrue(settings.getStorageLeaseSeconds() >= 0, String.format(
|
||||||
|
OBJ_FIELD_GTE_0, "OrgLeaseSettings", "storageLeaseSeconds", settings.getStorageLeaseSeconds()));
|
||||||
|
}
|
||||||
|
if (settings.getDeploymentLeaseSeconds() != null) {
|
||||||
|
assertTrue(settings.getDeploymentLeaseSeconds() >= 0, String.format(
|
||||||
|
OBJ_FIELD_GTE_0, "OrgLeaseSettings", "deploymentLeaseSeconds", settings.getDeploymentLeaseSeconds()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// parent type
|
||||||
|
checkResourceType(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void checkVAppTemplateLeaseSettings(OrgVAppTemplateLeaseSettings settings) {
|
||||||
|
// Check optional fields
|
||||||
|
// NOTE deleteOnStorageLeaseExpiration cannot be checked
|
||||||
|
if (settings.getStorageLeaseSeconds() != null) {
|
||||||
|
assertTrue(settings.getStorageLeaseSeconds() >= 0, String.format(
|
||||||
|
OBJ_FIELD_GTE_0, "OrgVAppTemplateLeaseSettings", "storageLeaseSeconds", settings.getStorageLeaseSeconds()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// parent type
|
||||||
|
checkResourceType(settings);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue