mirror of https://github.com/apache/jclouds.git
JCLOUDS-199. CloudStack live tests against ACS 4.2 simulator cleanup.
- ACS 4.x doesn't like taking SSH pub keys from the filesystem, so generate them on the fly. - vm.getDisplayName() can be null now. - Add new possible resource limit types. - Default to looking template=osFamily=CENTOS, since that's the only template guaranteed to be there in the simulator. - Use adminJobComplete instead of jobComplete in admin tests - Accept capacity/usage/etc of 0. - Premium configuration category not present in ACS. - Sleep a bit between deleting a domain and verifying it's not there any more. Also expect an IllegalStateException. - Given that there are issues deleting zones at the moment (through the UI, too), use a different zone for pod and zone tests. Still failing tests: - pretty much everything that creates a VM and expects to log into it, but that's simulator-specific. - Zone deletion, due to a bug in ACS, apparently. - Registering and creating templates - creating volumes from snapshots, and attaching volumes
This commit is contained in:
parent
2c6d8b2479
commit
48b499c636
|
@ -55,7 +55,7 @@
|
||||||
<test.cloudstack.domainAdminCredential />
|
<test.cloudstack.domainAdminCredential />
|
||||||
<test.cloudstack.globalAdminIdentity />
|
<test.cloudstack.globalAdminIdentity />
|
||||||
<test.cloudstack.globalAdminCredential />
|
<test.cloudstack.globalAdminCredential />
|
||||||
<test.cloudstack.template />
|
<test.cloudstack.template>osFamily=CENTOS</test.cloudstack.template>
|
||||||
<jclouds.osgi.export>org.jclouds.cloudstack*;version="${project.version}"</jclouds.osgi.export>
|
<jclouds.osgi.export>org.jclouds.cloudstack*;version="${project.version}"</jclouds.osgi.export>
|
||||||
<jclouds.osgi.import>
|
<jclouds.osgi.import>
|
||||||
org.jclouds.compute.internal;version="${project.version}",
|
org.jclouds.compute.internal;version="${project.version}",
|
||||||
|
|
|
@ -104,7 +104,9 @@ public class VirtualMachineToNodeMetadata implements Function<VirtualMachine, No
|
||||||
// on hosts not started with jclouds
|
// on hosts not started with jclouds
|
||||||
builder.hostname(from.getDisplayName());
|
builder.hostname(from.getDisplayName());
|
||||||
builder.location(FluentIterable.from(locations.get()).firstMatch(idEquals(from.getZoneId())).orNull());
|
builder.location(FluentIterable.from(locations.get()).firstMatch(idEquals(from.getZoneId())).orNull());
|
||||||
|
if (from.getDisplayName() != null) {
|
||||||
builder.group(nodeNamingConvention.groupInUniqueNameOrNull(from.getDisplayName()));
|
builder.group(nodeNamingConvention.groupInUniqueNameOrNull(from.getDisplayName()));
|
||||||
|
}
|
||||||
Image image = FluentIterable.from(images.get()).firstMatch(new Predicate<Image>() {
|
Image image = FluentIterable.from(images.get()).firstMatch(new Predicate<Image>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Image input) {
|
public boolean apply(Image input) {
|
||||||
|
|
|
@ -60,6 +60,34 @@ public class ResourceLimit {
|
||||||
* 4 - Template. Number of templates that a user can register/create.
|
* 4 - Template. Number of templates that a user can register/create.
|
||||||
*/
|
*/
|
||||||
TEMPLATE(4),
|
TEMPLATE(4),
|
||||||
|
/**
|
||||||
|
* 5 - Projects.
|
||||||
|
*/
|
||||||
|
PROJECT(5),
|
||||||
|
/**
|
||||||
|
* 6 - Networks.
|
||||||
|
*/
|
||||||
|
NETWORK(6),
|
||||||
|
/**
|
||||||
|
* 7 - VPC. Number of VPC the user can own.
|
||||||
|
*/
|
||||||
|
VPC(7),
|
||||||
|
/**
|
||||||
|
* 8 - CPU. The number of CPUs the user can allocate.
|
||||||
|
*/
|
||||||
|
CPU(8),
|
||||||
|
/**
|
||||||
|
* 9 - Memory. The amount of memory the user can allocate.
|
||||||
|
*/
|
||||||
|
MEMORY(9),
|
||||||
|
/**
|
||||||
|
* 10 - Primary storage.
|
||||||
|
*/
|
||||||
|
PRIMARY_STORAGE(10),
|
||||||
|
/**
|
||||||
|
* 11 - Secondary storage.
|
||||||
|
*/
|
||||||
|
SECONDARY_STORAGE(11),
|
||||||
|
|
||||||
UNRECOGNIZED(Integer.MAX_VALUE);
|
UNRECOGNIZED(Integer.MAX_VALUE);
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,6 @@ import org.jclouds.cloudstack.suppliers.NetworksForCurrentUser;
|
||||||
import org.jclouds.cloudstack.suppliers.ZoneIdToZoneSupplier;
|
import org.jclouds.cloudstack.suppliers.ZoneIdToZoneSupplier;
|
||||||
import org.jclouds.collect.Memoized;
|
import org.jclouds.collect.Memoized;
|
||||||
import org.jclouds.compute.ComputeServiceAdapter.NodeAndInitialCredentials;
|
import org.jclouds.compute.ComputeServiceAdapter.NodeAndInitialCredentials;
|
||||||
import org.jclouds.compute.ComputeTestUtils;
|
|
||||||
import org.jclouds.compute.config.ComputeServiceAdapterContextModule;
|
import org.jclouds.compute.config.ComputeServiceAdapterContextModule;
|
||||||
import org.jclouds.compute.domain.NodeMetadata;
|
import org.jclouds.compute.domain.NodeMetadata;
|
||||||
import org.jclouds.compute.domain.Template;
|
import org.jclouds.compute.domain.Template;
|
||||||
|
@ -73,6 +72,7 @@ import org.jclouds.compute.strategy.PrioritizeCredentialsFromTemplate;
|
||||||
import org.jclouds.domain.Credentials;
|
import org.jclouds.domain.Credentials;
|
||||||
import org.jclouds.location.Provider;
|
import org.jclouds.location.Provider;
|
||||||
import org.jclouds.logging.slf4j.config.SLF4JLoggingModule;
|
import org.jclouds.logging.slf4j.config.SLF4JLoggingModule;
|
||||||
|
import org.jclouds.ssh.SshKeys;
|
||||||
import org.testng.annotations.AfterGroups;
|
import org.testng.annotations.AfterGroups;
|
||||||
import org.testng.annotations.BeforeGroups;
|
import org.testng.annotations.BeforeGroups;
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
@ -116,7 +116,7 @@ public class CloudStackComputeServiceAdapterLiveTest extends BaseCloudStackApiLi
|
||||||
CloudStackComputeServiceAdapter.class);
|
CloudStackComputeServiceAdapter.class);
|
||||||
|
|
||||||
keyPairName = prefix + "-adapter-test-keypair";
|
keyPairName = prefix + "-adapter-test-keypair";
|
||||||
keyPair = ComputeTestUtils.setupKeyPair();
|
keyPair = SshKeys.generate();
|
||||||
|
|
||||||
client.getSSHKeyPairApi().deleteSSHKeyPair(keyPairName);
|
client.getSSHKeyPairApi().deleteSSHKeyPair(keyPairName);
|
||||||
client.getSSHKeyPairApi().registerSSHKeyPair(keyPairName, keyPair.get("public"));
|
client.getSSHKeyPairApi().registerSSHKeyPair(keyPairName, keyPair.get("public"));
|
||||||
|
|
|
@ -46,7 +46,7 @@ public class DomainAccountApiLiveTest extends BaseCloudStackApiLiveTest {
|
||||||
AsyncCreateResponse response = domainAdminClient.getAccountApi()
|
AsyncCreateResponse response = domainAdminClient.getAccountApi()
|
||||||
.disableAccount(testAccount.getName(), testAccount.getDomainId(), false);
|
.disableAccount(testAccount.getName(), testAccount.getDomainId(), false);
|
||||||
assertNotNull(response);
|
assertNotNull(response);
|
||||||
assertTrue(jobComplete.apply(response.getJobId()));
|
assertTrue(adminJobComplete.apply(response.getJobId()));
|
||||||
|
|
||||||
AsyncJob<Account> job = domainAdminClient.getAsyncJobApi().getAsyncJob(response.getJobId());
|
AsyncJob<Account> job = domainAdminClient.getAsyncJobApi().getAsyncJob(response.getJobId());
|
||||||
assertEquals(job.getResult().getState(), Account.State.DISABLED);
|
assertEquals(job.getResult().getState(), Account.State.DISABLED);
|
||||||
|
|
|
@ -69,7 +69,7 @@ public class DomainUserApiLiveTest extends BaseCloudStackApiLiveTest {
|
||||||
|
|
||||||
AsyncCreateResponse response = domainAdminClient.getUserClient().disableUser(testUser.getId());
|
AsyncCreateResponse response = domainAdminClient.getUserClient().disableUser(testUser.getId());
|
||||||
assertNotNull(response);
|
assertNotNull(response);
|
||||||
assertTrue(jobComplete.apply(response.getJobId()));
|
assertTrue(adminJobComplete.apply(response.getJobId()));
|
||||||
|
|
||||||
AsyncJob<User> job = domainAdminClient.getAsyncJobApi().getAsyncJob(response.getJobId());
|
AsyncJob<User> job = domainAdminClient.getAsyncJobApi().getAsyncJob(response.getJobId());
|
||||||
assertNotNull(job);
|
assertNotNull(job);
|
||||||
|
|
|
@ -39,7 +39,7 @@ public class GlobalAlertApiLiveTest extends BaseCloudStackApiLiveTest {
|
||||||
public void testListAlerts() throws Exception {
|
public void testListAlerts() throws Exception {
|
||||||
skipIfNotGlobalAdmin();
|
skipIfNotGlobalAdmin();
|
||||||
|
|
||||||
final Set<Alert> response = globalAdminClient.getAlertClient().listAlerts(ListAlertsOptions.Builder.id("20"));
|
final Set<Alert> response = globalAdminClient.getAlertClient().listAlerts();
|
||||||
assert null != response;
|
assert null != response;
|
||||||
assertTrue(response.size() >= 0);
|
assertTrue(response.size() >= 0);
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
|
@ -43,8 +43,8 @@ public class GlobalCapacityApiLiveTest extends BaseCloudStackApiLiveTest {
|
||||||
assertNotEquals(0, response.size());
|
assertNotEquals(0, response.size());
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (Capacity capacity : response) {
|
for (Capacity capacity : response) {
|
||||||
assertTrue(capacity.getCapacityTotal() > 0);
|
assertTrue(capacity.getCapacityTotal() >= 0);
|
||||||
assertTrue(capacity.getCapacityUsed() > 0);
|
assertTrue(capacity.getCapacityUsed() >= 0);
|
||||||
assertTrue(capacity.getPercentUsed() >= 0);
|
assertTrue(capacity.getPercentUsed() >= 0);
|
||||||
assertNotEquals(Capacity.Type.UNRECOGNIZED, capacity.getType());
|
assertNotEquals(Capacity.Type.UNRECOGNIZED, capacity.getType());
|
||||||
assertNotNull(capacity.getZoneName());
|
assertNotNull(capacity.getZoneName());
|
||||||
|
|
|
@ -53,7 +53,7 @@ public class GlobalConfigurationApiLiveTest extends BaseCloudStackApiLiveTest {
|
||||||
categories.add(entry.getCategory());
|
categories.add(entry.getCategory());
|
||||||
}
|
}
|
||||||
|
|
||||||
assert categories.containsAll(ImmutableSet.<Object>of("Network", "Advanced", "Premium",
|
assert categories.containsAll(ImmutableSet.<Object>of("Network", "Advanced",
|
||||||
"Storage", "Usage", "Snapshots", "Account Defaults", "Console Proxy", "Alert"));
|
"Storage", "Usage", "Snapshots", "Account Defaults", "Console Proxy", "Alert"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ public class GlobalConfigurationApiLiveTest extends BaseCloudStackApiLiveTest {
|
||||||
assertEquals(entry, getEntryByName(globalAdminClient.getConfigurationApi()
|
assertEquals(entry, getEntryByName(globalAdminClient.getConfigurationApi()
|
||||||
.listConfigurationEntries(name(entry.getName())), entry.getName()));
|
.listConfigurationEntries(name(entry.getName())), entry.getName()));
|
||||||
assert entry.getCategory() != null : entry;
|
assert entry.getCategory() != null : entry;
|
||||||
assert entry.getDescription() != null : entry;
|
// Description apparently can be null, so ... assert entry.getDescription() != null : entry;
|
||||||
assert entry.getName() != null : entry;
|
assert entry.getName() != null : entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,8 +50,8 @@ public class GlobalDomainApiLiveTest extends BaseCloudStackApiLiveTest {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test(expectedExceptions = IllegalStateException.class)
|
||||||
public void testCreateUpdateDeleteDomain() {
|
public void testCreateUpdateDeleteDomain() throws InterruptedException {
|
||||||
skipIfNotDomainAdmin();
|
skipIfNotDomainAdmin();
|
||||||
|
|
||||||
Domain domain = null;
|
Domain domain = null;
|
||||||
|
@ -68,6 +68,7 @@ public class GlobalDomainApiLiveTest extends BaseCloudStackApiLiveTest {
|
||||||
domainClient.deleteDomainAndAttachedResources(domain.getId());
|
domainClient.deleteDomainAndAttachedResources(domain.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Thread.sleep(5000);
|
||||||
assertNull(domainClient.getDomainById(domain.getId()));
|
assertNull(domainClient.getDomainById(domain.getId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,6 @@ public class GlobalHostApiLiveTest extends BaseCloudStackApiLiveTest {
|
||||||
assert host.getAverageLoad() >= 0;
|
assert host.getAverageLoad() >= 0;
|
||||||
assert host.getHypervisor() != null;
|
assert host.getHypervisor() != null;
|
||||||
}
|
}
|
||||||
assert host.getAllocationState() != null;
|
|
||||||
assert host.getEvents() != null;
|
assert host.getEvents() != null;
|
||||||
if (host.getType() == Host.Type.SECONDARY_STORAGE_VM) {
|
if (host.getType() == Host.Type.SECONDARY_STORAGE_VM) {
|
||||||
assert host.getName().startsWith("s-");
|
assert host.getName().startsWith("s-");
|
||||||
|
|
|
@ -78,14 +78,14 @@ public class GlobalPodApiLiveTest extends BaseCloudStackApiLiveTest {
|
||||||
public void testCreatePod() {
|
public void testCreatePod() {
|
||||||
skipIfNotGlobalAdmin();
|
skipIfNotGlobalAdmin();
|
||||||
|
|
||||||
zone = globalAdminClient.getZoneApi().createZone(prefix + "-zone", NetworkType.BASIC, "8.8.8.8", "10.10.10.10");
|
zone = globalAdminClient.getZoneApi().createZone(prefix + "-zone-for-pod", NetworkType.BASIC, "8.8.8.8", "10.10.10.10");
|
||||||
pod = globalAdminClient.getPodClient().createPod(prefix + "-pod", zone.getId(), "172.20.0.1", "172.20.0.250", "172.20.0.254", "255.255.255.0",
|
pod = globalAdminClient.getPodClient().createPod(prefix + "-pod", zone.getId(), "172.20.0.1", "172.20.0.250", "172.20.0.254", "255.255.255.0",
|
||||||
CreatePodOptions.Builder.allocationState(AllocationState.ENABLED));
|
CreatePodOptions.Builder.allocationState(AllocationState.ENABLED));
|
||||||
|
|
||||||
assertNotNull(pod);
|
assertNotNull(pod);
|
||||||
assertEquals(pod.getName(), prefix + "-pod");
|
assertEquals(pod.getName(), prefix + "-pod");
|
||||||
assertEquals(pod.getZoneId(), zone.getId());
|
assertEquals(pod.getZoneId(), zone.getId());
|
||||||
assertEquals(pod.getZoneName(), prefix + "-zone");
|
assertEquals(pod.getZoneName(), prefix + "-zone-for-pod");
|
||||||
assertEquals(pod.getStartIp(), "172.20.0.1");
|
assertEquals(pod.getStartIp(), "172.20.0.1");
|
||||||
assertEquals(pod.getEndIp(), "172.20.0.250");
|
assertEquals(pod.getEndIp(), "172.20.0.250");
|
||||||
assertEquals(pod.getGateway(), "172.20.0.254");
|
assertEquals(pod.getGateway(), "172.20.0.254");
|
||||||
|
@ -107,7 +107,7 @@ public class GlobalPodApiLiveTest extends BaseCloudStackApiLiveTest {
|
||||||
assertNotNull(updated);
|
assertNotNull(updated);
|
||||||
assertEquals(updated.getName(), prefix + "-updatedpod");
|
assertEquals(updated.getName(), prefix + "-updatedpod");
|
||||||
assertEquals(updated.getZoneId(), zone.getId());
|
assertEquals(updated.getZoneId(), zone.getId());
|
||||||
assertEquals(updated.getZoneName(), prefix + "-zone");
|
assertEquals(updated.getZoneName(), prefix + "-zone-for-pod");
|
||||||
assertEquals(updated.getStartIp(), "172.21.0.129");
|
assertEquals(updated.getStartIp(), "172.21.0.129");
|
||||||
assertEquals(updated.getEndIp(), "172.21.0.250");
|
assertEquals(updated.getEndIp(), "172.21.0.250");
|
||||||
assertEquals(updated.getGateway(), "172.21.0.254");
|
assertEquals(updated.getGateway(), "172.21.0.254");
|
||||||
|
|
|
@ -54,7 +54,7 @@ public class GlobalUsageApiLiveTest extends BaseCloudStackApiLiveTest {
|
||||||
|
|
||||||
Set<UsageRecord> records = globalAdminClient.getUsageClient().listUsageRecords(start, end, ListUsageRecordsOptions.NONE);
|
Set<UsageRecord> records = globalAdminClient.getUsageClient().listUsageRecords(start, end, ListUsageRecordsOptions.NONE);
|
||||||
assertNotNull(records);
|
assertNotNull(records);
|
||||||
assertTrue(records.size() > 0);
|
assertTrue(records.size() >= 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,9 +89,10 @@ public class GlobalVlanApiLiveTest extends BaseCloudStackApiLiveTest {
|
||||||
skipIfNotGlobalAdmin();
|
skipIfNotGlobalAdmin();
|
||||||
|
|
||||||
final Zone zone = Iterables.find(client.getZoneApi().listZones(), ZonePredicates.supportsAdvancedNetworks());
|
final Zone zone = Iterables.find(client.getZoneApi().listZones(), ZonePredicates.supportsAdvancedNetworks());
|
||||||
final NetworkOffering offering = find(client.getOfferingApi().listNetworkOfferings(),
|
final NetworkOffering offering = Iterables.tryFind(client.getOfferingApi().listNetworkOfferings(),
|
||||||
NetworkOfferingPredicates.supportsGuestVirtualNetworks());
|
NetworkOfferingPredicates.supportsGuestVirtualNetworks()).orNull();
|
||||||
|
|
||||||
|
if (offering != null) {
|
||||||
Set<Network> suitableNetworks = Sets.filter(client.getNetworkApi().listNetworks(
|
Set<Network> suitableNetworks = Sets.filter(client.getNetworkApi().listNetworks(
|
||||||
zoneId(zone.getId()).isSystem(false).trafficType(TrafficType.GUEST)),
|
zoneId(zone.getId()).isSystem(false).trafficType(TrafficType.GUEST)),
|
||||||
new Predicate<Network>() {
|
new Predicate<Network>() {
|
||||||
|
@ -118,6 +119,7 @@ public class GlobalVlanApiLiveTest extends BaseCloudStackApiLiveTest {
|
||||||
.networkId(network.getId())
|
.networkId(network.getId())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@AfterGroups(groups = "live")
|
@AfterGroups(groups = "live")
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -181,7 +181,7 @@ public class VirtualMachineApiLiveTest extends BaseCloudStackApiLiveTest {
|
||||||
if (vm.getPassword() != null) {
|
if (vm.getPassword() != null) {
|
||||||
conditionallyCheckSSH();
|
conditionallyCheckSSH();
|
||||||
}
|
}
|
||||||
assert in(ImmutableSet.of("NetworkFilesystem", "IscsiLUN", "VMFS", "PreSetup"))
|
assert in(ImmutableSet.of("ROOT", "NetworkFilesystem", "IscsiLUN", "VMFS", "PreSetup"))
|
||||||
.apply(vm.getRootDeviceType()) : vm;
|
.apply(vm.getRootDeviceType()) : vm;
|
||||||
checkVm(vm);
|
checkVm(vm);
|
||||||
}
|
}
|
||||||
|
@ -349,7 +349,7 @@ public class VirtualMachineApiLiveTest extends BaseCloudStackApiLiveTest {
|
||||||
assertEquals(vm.getId(), client.getVirtualMachineApi().getVirtualMachine(vm.getId()).getId());
|
assertEquals(vm.getId(), client.getVirtualMachineApi().getVirtualMachine(vm.getId()).getId());
|
||||||
assert vm.getId() != null : vm;
|
assert vm.getId() != null : vm;
|
||||||
assert vm.getName() != null : vm;
|
assert vm.getName() != null : vm;
|
||||||
assert vm.getDisplayName() != null : vm;
|
// vm.getDisplayName() can be null, so skip that check.
|
||||||
assert vm.getAccount() != null : vm;
|
assert vm.getAccount() != null : vm;
|
||||||
assert vm.getDomain() != null : vm;
|
assert vm.getDomain() != null : vm;
|
||||||
assert vm.getDomainId() != null : vm;
|
assert vm.getDomainId() != null : vm;
|
||||||
|
|
|
@ -1 +1,9 @@
|
||||||
{ "listresourcelimitsresponse" : { "count":5 ,"resourcelimit" : [ {"account":"jclouds","domainid":457,"domain":"AA000062-jclouds-dev","resourcetype":"0","max":-1}, {"account":"jclouds","domainid":457,"domain":"AA000062-jclouds-dev","resourcetype":"1","max":-1}, {"account":"jclouds","domainid":457,"domain":"AA000062-jclouds-dev","resourcetype":"2","max":-1}, {"account":"jclouds","domainid":457,"domain":"AA000062-jclouds-dev","resourcetype":"3","max":-1}, {"account":"jclouds","domainid":457,"domain":"AA000062-jclouds-dev","resourcetype":"4","max":-1} ] } }
|
{ "listresourcelimitsresponse" : { "count":8 ,"resourcelimit" : [
|
||||||
|
{"account":"jclouds","domainid":457,"domain":"AA000062-jclouds-dev","resourcetype":"0","max":-1},
|
||||||
|
{"account":"jclouds","domainid":457,"domain":"AA000062-jclouds-dev","resourcetype":"1","max":-1},
|
||||||
|
{"account":"jclouds","domainid":457,"domain":"AA000062-jclouds-dev","resourcetype":"2","max":-1},
|
||||||
|
{"account":"jclouds","domainid":457,"domain":"AA000062-jclouds-dev","resourcetype":"3","max":-1},
|
||||||
|
{"account":"jclouds","domainid":457,"domain":"AA000062-jclouds-dev","resourcetype":"4","max":-1},
|
||||||
|
{"account":"jclouds","domainid":457,"domain":"AA000062-jclouds-dev","resourcetype":"7","max":-1},
|
||||||
|
{"account":"jclouds","domainid":457,"domain":"AA000062-jclouds-dev","resourcetype":"8","max":-1},
|
||||||
|
{"account":"jclouds","domainid":457,"domain":"AA000062-jclouds-dev","resourcetype":"9","max":-1}] } }
|
||||||
|
|
Loading…
Reference in New Issue