Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,26 @@ Then:
To create packages for noredist add the `-p noredist` option to the package script.
All the rpm packages will be created in `dist/rpmbuild/RPMS/x86_64` directory.

## Management server identity

The server saves its ID in `${user.home}/.cloudstack/management-server.id`
and reuses it after restarts. Keep this file when upgrading or restoring the
same server. Do not copy it to a different management server.

The service account needs write access to the directory and its `.lock` file.
Startup fails if the ID cannot be read, validated, or saved.

These options can be added to `JAVA_OPTS` in `/etc/default/cloudstack-management`:

* `-Dmanagement.server.id.file=/path/to/management-server.id` chooses a different
persistent local file location.
* `-Dmanagement.server.id=123456789` overrides the ID with a positive number
unique to this server. It does not change the saved file.

If interface order already changed before upgrading, set `management.server.id`
to this server's original `msid` from `cloud.mshost` before the first start.
Keep that override configured on later starts.

## Notes

If you will be using Xen as your hypervisor, please download [vhd-util](https://download.cloudstack.org/tools/vhd-util)
Expand Down
7 changes: 7 additions & 0 deletions PendingReleaseNotes
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ example.ver.1 > example.ver.2:
Storage to grow to enormous sizes as Linux Distributions keep growing in
size while a stripped down Linux should fit on a 2.88MB floppy.

4.22.1.0 > 4.22.2.0:
* Management servers now reuse a saved ID after restarts, so network interface
reordering does not change their identity. Keep the ID file during upgrades
and restores. See INSTALL.md for the file location and ID settings.
* Old non-Up management server records no longer cause agents to avoid an IP
that also has an Up record.

4.22.0.0 > 4.22.0.1:
* Disk-only instance snapshots for KVM UEFI VMs now include a sidecar copy of
the active NVRAM state so revert operations restore both disk and firmware
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.TimeZone;


Expand All @@ -35,7 +38,6 @@
import com.cloud.utils.db.DB;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.GenericSearchBuilder;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.TransactionLegacy;
Expand All @@ -47,7 +49,6 @@ public class ManagementServerHostDaoImpl extends GenericDaoBase<ManagementServer
private final SearchBuilder<ManagementServerHostVO> ActiveSearch;
private final SearchBuilder<ManagementServerHostVO> InactiveSearch;
private final SearchBuilder<ManagementServerHostVO> StateSearch;
protected GenericSearchBuilder<ManagementServerHostVO, String> NonUpStateMsSearch;

@Override
public void invalidateRunSession(long id, long runid) {
Expand Down Expand Up @@ -229,11 +230,6 @@ protected ManagementServerHostDaoImpl() {
StateSearch.and("state", StateSearch.entity().getState(), SearchCriteria.Op.IN);
StateSearch.and("runid", StateSearch.entity().getRunid(), SearchCriteria.Op.GT);
StateSearch.done();

NonUpStateMsSearch = createSearchBuilder(String.class);
NonUpStateMsSearch.selectFields(NonUpStateMsSearch.entity().getServiceIP());
NonUpStateMsSearch.and("state", NonUpStateMsSearch.entity().getState(), SearchCriteria.Op.NLIKE);
NonUpStateMsSearch.done();
}

@Override
Expand Down Expand Up @@ -267,11 +263,27 @@ public List<ManagementServerHostVO> listBy(ManagementServerHost.State... states)
return listBy(sc);
}

// The saved ID stops future ID changes but leaves old records behind
// This method handles duplicate records that already exist
// Old Down records must not make agents skip an address that also has an Up record
@Override
public List<String> listNonUpStateMsIPs() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MashrafeeAryan instead of skipping old down rows in the avoid list, can we just mark old rows with the same ip as removed when the ms registers? fixes the leftover rows too, like in #14448

SearchCriteria<String> sc = NonUpStateMsSearch.create();
sc.addAnd("state", SearchCriteria.Op.NLIKE, State.Up);
return customSearch(sc, null);
Set<String> upIPs = new HashSet<>();
Set<String> nonUpIPs = new LinkedHashSet<>();
// Read server records that have not been removed
for (ManagementServerHostVO host : listAll()) {
if (State.Up.equals(host.getState())) {
// Collect addresses marked Up
upIPs.add(host.getServiceIP());
} else {
// Collect addresses in other states
nonUpIPs.add(host.getServiceIP());
}
}
// Keep every Up address out of the avoid list
nonUpIPs.removeAll(upIPs);
// Give agents the remaining addresses to skip
return new ArrayList<>(nonUpIPs);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// 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.cluster.dao;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.apache.cloudstack.management.ManagementServerHost.State;
import org.junit.Test;

import com.cloud.cluster.ManagementServerHostVO;

// Checks that old server records do not make agents skip an available address
public class ManagementServerHostDaoImplTest {
private final ManagementServerHostDaoImpl dao = spy(new ManagementServerHostDaoImpl());

// Do not avoid an address if one record says Up and an old record says Down
@Test
public void staleDownIdentityDoesNotBlockAnUpServerAtTheSameIp() {
ManagementServerHostVO down = host("10.0.0.1", State.Down);
ManagementServerHostVO up = host("10.0.0.1", State.Up);
assertAvoided(Arrays.asList(down, up));
assertAvoided(Arrays.asList(up, down));
}

// Keep unavailable addresses in the avoid list and include each address only once
@Test
public void unavailableAddressesRemainAvoidedWithoutDuplicates() {
assertAvoided(Arrays.asList(host("10.0.0.1", State.Down), host("10.0.0.1", State.Down),
host("10.0.0.2", State.Maintenance), host("10.0.0.3", State.Up)), "10.0.0.1", "10.0.0.2");
}

// Allow an address marked Up even if an old record says it is in maintenance
@Test
public void upServerTakesPrecedenceOverAnOldMaintenanceRecord() {
assertAvoided(Arrays.asList(host("10.0.0.1", State.Maintenance), host("10.0.0.1", State.Up)));
}

// No server records means there are no addresses to avoid
@Test
public void emptyServerListHasNoAvoidedAddresses() {
assertAvoided(Collections.emptyList());
}

// Run the avoid-list code with pretend records and check the returned addresses
private void assertAvoided(List<ManagementServerHostVO> hosts, String... expected) {
doReturn(hosts).when(dao).listAll();
assertEquals(Arrays.asList(expected), dao.listNonUpStateMsIPs());
}

// Make a pretend server record with an address and a state
private ManagementServerHostVO host(String ip, State state) {
ManagementServerHostVO host = new ManagementServerHostVO();
host.setServiceIP(ip);
host.setState(state);
return host;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import javax.inject.Inject;
import javax.naming.ConfigurationException;

import org.apache.cloudstack.utils.identity.ManagementServerNode;

import org.springframework.stereotype.Component;

import org.apache.cloudstack.api.command.admin.usage.AddTrafficMonitorCmd;
Expand Down Expand Up @@ -88,7 +90,6 @@
import com.cloud.utils.db.TransactionCallbackNoReturn;
import com.cloud.utils.db.TransactionStatus;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.net.MacAddress;

@Component
public class NetworkUsageManagerImpl extends ManagerBase implements NetworkUsageService, NetworkUsageManager, ResourceStateAdapter {
Expand Down Expand Up @@ -251,7 +252,8 @@ protected class DirectNetworkStatsListener implements Listener {

private int _interval;

private final long mgmtSrvrId = MacAddress.getMacAddress().toLong();
// Use the saved ID to check which traffic monitors this server is in charge of
private final long mgmtSrvrId = ManagementServerNode.getManagementServerId();

protected DirectNetworkStatsListener(int interval) {
_interval = interval;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,6 @@
import com.cloud.utils.fsm.StateListener;
import com.cloud.utils.fsm.StateMachine2;
import com.cloud.utils.net.Ip;
import com.cloud.utils.net.MacAddress;
import com.cloud.utils.net.NetUtils;
import com.cloud.vm.DomainRouterVO;
import com.cloud.vm.Nic;
Expand Down Expand Up @@ -365,7 +364,8 @@ public class VirtualNetworkApplianceManagerImpl extends ManagerBase implements V
private int _routerExtraPublicNics = 2;
private int _usageAggregationRange = 1440;
private String _usageTimeZone = "GMT";
private final long mgmtSrvrId = MacAddress.getMacAddress().toLong();
// Use the saved ID to find the routers this server is in charge of
private final long mgmtSrvrId = ManagementServerNode.getManagementServerId();
private static final int ACQUIRE_GLOBAL_LOCK_TIMEOUT_FOR_COOPERATION = 5; // 5 seconds
private boolean _dailyOrHourly = false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -847,7 +847,6 @@
import com.cloud.utils.db.TransactionStatus;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.fsm.StateMachine2;
import com.cloud.utils.net.MacAddress;
import com.cloud.utils.net.NetUtils;
import com.cloud.utils.security.CertificateHelper;
import com.cloud.utils.ssh.SSHKeysHelper;
Expand Down Expand Up @@ -1198,9 +1197,10 @@ public DetailVO findDetail(final long hostId, final String name) {
return _detailsDao.findDetail(hostId, name);
}

// Return the persistent server ID shared by the other CloudStack components
@Override
public long getId() {
return MacAddress.getMacAddress().toLong();
return ManagementServerNode.getManagementServerId();
}

protected void checkPortParameters(final String publicPort, final String privatePort, final String privateIp, final String proto) {
Expand Down
4 changes: 2 additions & 2 deletions server/src/main/java/com/cloud/server/StatsCollector.java
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@
import com.cloud.utils.db.TransactionCallbackNoReturn;
import com.cloud.utils.db.TransactionStatus;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.net.MacAddress;
import com.cloud.utils.script.Script;
import com.cloud.vm.NicVO;
import com.cloud.vm.UserVmManager;
Expand Down Expand Up @@ -391,7 +390,8 @@ public String toString() {
private ScheduledExecutorService _diskStatsUpdateExecutor;
private int _usageAggregationRange = 1440;
private String _usageTimeZone = "GMT";
private final long mgmtSrvrId = MacAddress.getMacAddress().toLong();
// Use the saved ID to check which stats jobs this server should do
private final long mgmtSrvrId = ManagementServerNode.getManagementServerId();
private static final int ACQUIRE_GLOBAL_LOCK_TIMEOUT_FOR_COOPERATION = 5; // 5 seconds
private boolean _dailyOrHourly = false;
protected long managementServerNodeId = ManagementServerNode.getManagementServerId();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// 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.server;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;

import org.apache.cloudstack.utils.identity.ManagementServerNode;
import org.junit.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.springframework.test.util.ReflectionTestUtils;

import com.cloud.cluster.ManagementServerHostVO;
import com.cloud.cluster.dao.ManagementServerHostDao;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GlobalLock;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallbackNoReturn;
import com.cloud.utils.net.MacAddress;

// Check that the server and its stats jobs use the saved ID
public class ManagementServerIdentityTest {
// Use different values for the saved ID and the ID from the network card
private static final long SAVED_ID = 123L;
private static final long CHANGED_MAC_ID = 456L;

// Return the saved ID even when the network card gives a different ID
@Test
public void managementServiceReportsSavedIdAfterMacChanges() {
try (MockedStatic<ManagementServerNode> node = mockStatic(ManagementServerNode.class);
MockedStatic<MacAddress> mac = mockStatic(MacAddress.class)) {
node.when(ManagementServerNode::getManagementServerId).thenReturn(SAVED_ID);
mac.when(MacAddress::getMacAddress).thenReturn(new MacAddress(CHANGED_MAC_ID));

ManagementServerImpl server = mock(ManagementServerImpl.class, Mockito.CALLS_REAL_METHODS);
assertEquals(SAVED_ID, server.getId());
}
}

// Still start the disk stats job when the saved ID says this server is in charge
@Test
public void owningServerStillUpdatesDiskStatsAfterMacChanges() {
StatsCollector previous = StatsCollector.getInstance();
try (MockedStatic<ManagementServerNode> node = mockStatic(ManagementServerNode.class);
MockedStatic<MacAddress> mac = mockStatic(MacAddress.class);
MockedStatic<GlobalLock> locks = mockStatic(GlobalLock.class);
MockedStatic<Transaction> transactions = mockStatic(Transaction.class)) {
node.when(ManagementServerNode::getManagementServerId).thenReturn(SAVED_ID);
mac.when(MacAddress::getMacAddress).thenReturn(new MacAddress(CHANGED_MAC_ID));
GlobalLock lock = mock(GlobalLock.class);
locks.when(() -> GlobalLock.getInternLock("vm.disk.stats")).thenReturn(lock);
when(lock.lock(5)).thenReturn(true);

// Pretend the database says this server should do the job
ManagementServerHostVO host = new ManagementServerHostVO();
host.setMsid(SAVED_ID);
ManagementServerHostDao dao = mock(ManagementServerHostDao.class);
when(dao.findOneInUpState(any(Filter.class))).thenReturn(host);
StatsCollector collector = new StatsCollector();
ReflectionTestUtils.setField(collector, "managementServerHostDao", dao);

collector.new VmDiskStatsUpdaterTask().runInContext();

transactions.verify(() -> Transaction.execute(any(TransactionCallbackNoReturn.class)));
} finally {
// Put the old stats collector back so other tests are not affected
ReflectionTestUtils.setField(StatsCollector.class, "s_instance", previous);
}
}
}
Loading
Loading