From 39152323e3b0cbd33eee3b678422425b1127cc82 Mon Sep 17 00:00:00 2001 From: Wido den Hollander Date: Wed, 4 Sep 2024 08:44:17 +0200 Subject: [PATCH 1/3] kvm: Present the UUID of the VM as serial through smbios information (#9236) This PR makes sure a KVM VM gets the UUID of the VM as a static serialnumber through smbios. Some applications on primarily Windows servers require a stable serial number for licensing purposes. By providing this serial number we can make sure these applications can have a license configured. More information: https://libvirt.org/formatdomain.html#smbios-system-information --- .../java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java index dfd531b1a0b6..0f11c12f101f 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java @@ -209,6 +209,7 @@ public String toString() { guestDef.append("" + getManufacturer() +"\n"); guestDef.append("" + getProduct() + "\n"); guestDef.append("" + _uuid + "\n"); + guestDef.append("" + _uuid + "\n"); guestDef.append("\n"); guestDef.append("\n"); From f9451fce3a33d90c8a13716c69ebfd13aff6b077 Mon Sep 17 00:00:00 2001 From: Vishesh Date: Wed, 4 Sep 2024 12:53:57 +0530 Subject: [PATCH 2/3] Notify users when upgrades are available or restart is required for network or VPC (#7610) Co-authored-by: Harikrishna Co-authored-by: dahn --- ui/package.json | 1 + ui/public/locales/en.json | 6 ++ ui/src/components/header/HeaderNotice.vue | 8 ++- ui/src/components/page/GlobalFooter.vue | 22 +++++++ ui/src/components/view/ListView.vue | 7 +++ ui/src/components/view/SearchView.vue | 12 +++- ui/src/config/section/network.js | 4 +- ui/src/store/getters.js | 1 + ui/src/store/modules/user.js | 73 ++++++++++++++++++++++- ui/src/store/mutation-types.js | 1 + ui/src/utils/util.js | 10 ++++ 11 files changed, 138 insertions(+), 7 deletions(-) diff --git a/ui/package.json b/ui/package.json index 8a9a37e0490a..df8c5d5f82b7 100644 --- a/ui/package.json +++ b/ui/package.json @@ -60,6 +60,7 @@ "npm-check-updates": "^6.0.1", "nprogress": "^0.2.0", "qrious": "^4.0.2", + "semver": "^7.6.3", "vue": "^3.2.31", "vue-chartjs": "^4.0.7", "vue-clipboard2": "^0.3.1", diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index c87ee0070f77..1bdcc675346b 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -1446,6 +1446,7 @@ "label.network.offering": "Network offering", "label.network.offerings": "Network offerings", "label.network.policy": "Network Policy", +"label.network.restart.required": "Network restart required", "label.network.route.table": "Network route table", "label.network.routing.policy": "Network routing policy", "label.network.permissions": "Network permissions", @@ -1476,6 +1477,7 @@ "label.new.secondaryip.description": "Enter new secondary IP address", "label.new.tag": "New tag", "label.new.vm": "New Instance", +"label.new.version.available": "New version available", "label.newdiskoffering": "New offering", "label.newinstance": "New Instance", "label.newname": "New name", @@ -2463,6 +2465,7 @@ "label.vpc.id": "VPC ID", "label.vpc.offerings": "VPC offerings", "label.vpc.virtual.router": "VPC virtual router", +"label.vpc.restart.required": "VPC restart required", "label.vpcid": "VPC", "label.vpclimit": "VPC limits", "label.vpcname": "VPC", @@ -3185,6 +3188,7 @@ "message.network.offering.mac.learning.warning": "WARNING: In order to use MAC Learning you must ensure your hypervisor hosts are running ESXi 6.7+ and the Network uses distributed vSwitch 6.6.0+.", "message.network.offering.promiscuous.mode": "Applicable for guest Networks on VMware hypervisor only.\nReject - The switch drops any outbound frame from a virtual machine adapter with a source MAC address that is different from the one in the .vmx configuration file.\nAccept - The switch does not perform filtering, and permits all outbound frames.\nNone - Default to value from global setting.", "message.network.removenic": "Please confirm that want to remove this NIC, which will also remove the associated Network from the Instance.", +"message.network.restart.required": "Restart is required for network(s). Click here to view network(s) which require restart.", "message.network.secondaryip": "Please confirm that you would like to acquire a new secondary IP for this NIC. \n NOTE: You need to manually configure the newly-acquired secondary IP inside the virtual machine.", "message.network.selection": "Choose one or more Networks to attach the Instance to.", "message.network.selection.new.network": "A new Network can also be created here.", @@ -3192,6 +3196,7 @@ "message.network.usage.info.data.points": "Each data point represents the difference in data traffic since the last data point.", "message.network.usage.info.sum.of.vnics": "The Network usage shown is made up of the sum of data traffic from all the vNICs in the Instance.", "message.nfs.mount.options.description": "Comma separated list of NFS mount options for KVM hosts. Supported options : vers=[3,4.0,4.1,4.2], nconnect=[1...16]", +"message.new.version.available": "A new version of CloudStack is available. Click here to check the details", "message.no.data.to.show.for.period": "No data to show for the selected period.", "message.no.description": "No description entered.", "message.offering.internet.protocol.warning": "WARNING: IPv6 supported Networks use static routing and will require upstream routes to be configured manually.", @@ -3526,6 +3531,7 @@ "message.volume.state.primary.storage.suitability": "The suitability of a primary storage for a volume depends on the disk offering of the volume and on the virtual machine allocation (if the volume is attached to a virtual machine).", "message.volumes.managed": "Volumes controlled by CloudStack.", "message.volumes.unmanaged": "Volumes not controlled by CloudStack.", +"message.vpc.restart.required": "Restart is required for VPC(s). Click here to view VPC(s) which require restart.", "message.vr.alert.upon.network.offering.creation.l2": "As virtual routers are not created for L2 Networks, the compute offering will not be used.", "message.vr.alert.upon.network.offering.creation.others": "As none of the obligatory services for creating a virtual router (VPN, DHCP, DNS, Firewall, LB, UserData, SourceNat, StaticNat, PortForwarding) are enabled, the virtual router will not be created and the compute offering will not be used.", "message.warn.change.primary.storage.scope": "This feature is tested and supported for the following configurations:
KVM - NFS/Ceph - DefaultPrimary
VMware - NFS - DefaultPrimary
*There might be extra steps involved to make it work for other configurations.", diff --git a/ui/src/components/header/HeaderNotice.vue b/ui/src/components/header/HeaderNotice.vue index 82dc4d45edc5..f83ffcc538a9 100644 --- a/ui/src/components/header/HeaderNotice.vue +++ b/ui/src/components/header/HeaderNotice.vue @@ -47,10 +47,12 @@ diff --git a/ui/src/components/page/GlobalFooter.vue b/ui/src/components/page/GlobalFooter.vue index 854cecc78ace..a89e5f2d8b39 100644 --- a/ui/src/components/page/GlobalFooter.vue +++ b/ui/src/components/page/GlobalFooter.vue @@ -22,6 +22,15 @@
CloudStack {{ $store.getters.features.cloudstackversion }} + + + + + {{ $t('label.new.version.available') + ': ' + $store.getters.latestVersion.version }} + + @@ -32,11 +41,24 @@ diff --git a/ui/src/components/view/ListView.vue b/ui/src/components/view/ListView.vue index 27bd6a2fb363..2f94d6436a40 100644 --- a/ui/src/components/view/ListView.vue +++ b/ui/src/components/view/ListView.vue @@ -90,6 +90,13 @@ {{ text }} {{ text }} + +   + + + + + diff --git a/ui/src/components/view/SearchView.vue b/ui/src/components/view/SearchView.vue index 0d18ab9a85c2..8d702eafdcd7 100644 --- a/ui/src/components/view/SearchView.vue +++ b/ui/src/components/view/SearchView.vue @@ -303,7 +303,7 @@ export default { } if (['zoneid', 'domainid', 'imagestoreid', 'storageid', 'state', 'account', 'hypervisor', 'level', 'clusterid', 'podid', 'groupid', 'entitytype', 'accounttype', 'systemvmtype', 'scope', 'provider', - 'type', 'scope', 'managementserverid', 'serviceofferingid', 'diskofferingid', 'usagetype'].includes(item) + 'type', 'scope', 'managementserverid', 'serviceofferingid', 'diskofferingid', 'usagetype', 'restartrequired'].includes(item) ) { type = 'list' } else if (item === 'tags') { @@ -395,6 +395,16 @@ export default { this.fields[providerIndex].loading = false } + if (arrayField.includes('restartrequired')) { + const restartRequiredIndex = this.fields.findIndex(item => item.name === 'restartrequired') + this.fields[restartRequiredIndex].loading = true + this.fields[restartRequiredIndex].opts = [ + { id: 'true', name: 'label.yes' }, + { id: 'false', name: 'label.no' } + ] + this.fields[restartRequiredIndex].loading = false + } + if (arrayField.includes('resourcetype')) { const resourceTypeIndex = this.fields.findIndex(item => item.name === 'resourcetype') this.fields[resourceTypeIndex].loading = true diff --git a/ui/src/config/section/network.js b/ui/src/config/section/network.js index 26b4f279e3d1..1ff704a306f6 100644 --- a/ui/src/config/section/network.js +++ b/ui/src/config/section/network.js @@ -54,7 +54,7 @@ export default { return fields }, filters: ['all', 'account', 'domainpath', 'shared'], - searchFilters: ['keyword', 'zoneid', 'domainid', 'account', 'type', 'tags'], + searchFilters: ['keyword', 'zoneid', 'domainid', 'account', 'type', 'restartrequired', 'tags'], related: [{ name: 'vm', title: 'label.instances', @@ -218,7 +218,7 @@ export default { return fields }, details: ['name', 'id', 'displaytext', 'cidr', 'networkdomain', 'ip6routes', 'ispersistent', 'redundantvpcrouter', 'restartrequired', 'zonename', 'account', 'domain', 'dns1', 'dns2', 'ip6dns1', 'ip6dns2', 'publicmtu'], - searchFilters: ['name', 'zoneid', 'domainid', 'account', 'tags'], + searchFilters: ['name', 'zoneid', 'domainid', 'account', 'restartrequired', 'tags'], related: [{ name: 'vm', title: 'label.instances', diff --git a/ui/src/store/getters.js b/ui/src/store/getters.js index 67b168be8c28..405fd11bad11 100644 --- a/ui/src/store/getters.js +++ b/ui/src/store/getters.js @@ -28,6 +28,7 @@ const getters = { apis: state => state.user.apis, features: state => state.user.features, userInfo: state => state.user.info, + latestVersion: state => state.user.latestVersion, addRouters: state => state.permission.addRouters, multiTab: state => state.app.multiTab, listAllProjects: state => state.app.listAllProjects, diff --git a/ui/src/store/modules/user.js b/ui/src/store/modules/user.js index 24302a940335..a5b48acf99d7 100644 --- a/ui/src/store/modules/user.js +++ b/ui/src/store/modules/user.js @@ -18,12 +18,15 @@ import Cookies from 'js-cookie' import message from 'ant-design-vue/es/message' import notification from 'ant-design-vue/es/notification' +import semver from 'semver' import { vueProps } from '@/vue-app' import router from '@/router' import store from '@/store' import { oauthlogin, login, logout, api } from '@/api' import { i18n } from '@/locales' +import { axios } from '../../utils/request' +import { getParsedVersion } from '@/utils/util' import { ACCESS_TOKEN, @@ -38,7 +41,8 @@ import { DARK_MODE, CUSTOM_COLUMNS, OAUTH_DOMAIN, - OAUTH_PROVIDER + OAUTH_PROVIDER, + LATEST_CS_VERSION } from '@/store/mutation-types' const user = { @@ -167,6 +171,12 @@ const user = { }, SET_OAUTH_PROVIDER_USED_TO_LOGIN: (state, provider) => { vueProps.$localStorage.set(OAUTH_PROVIDER, provider) + }, + SET_LATEST_VERSION: (state, version) => { + if (version?.fetchedTs > 0) { + vueProps.$localStorage.set(LATEST_CS_VERSION, version) + state.latestVersion = version + } } }, @@ -212,6 +222,8 @@ const user = { commit('SET_2FA_PROVIDER', result.providerfor2fa) commit('SET_2FA_ISSUER', result.issuerfor2fa) commit('SET_LOGIN_FLAG', false) + const latestVersion = vueProps.$localStorage.get(LATEST_CS_VERSION, { version: '', fetchedTs: 0 }) + commit('SET_LATEST_VERSION', latestVersion) notification.destroy() resolve() @@ -259,6 +271,8 @@ const user = { commit('SET_2FA_PROVIDER', result.providerfor2fa) commit('SET_2FA_ISSUER', result.issuerfor2fa) commit('SET_LOGIN_FLAG', false) + const latestVersion = vueProps.$localStorage.get(LATEST_CS_VERSION, { version: '', fetchedTs: 0 }) + commit('SET_LATEST_VERSION', latestVersion) notification.destroy() resolve() @@ -277,10 +291,12 @@ const user = { const cachedCustomColumns = vueProps.$localStorage.get(CUSTOM_COLUMNS, {}) const domainStore = vueProps.$localStorage.get(DOMAIN_STORE, {}) const darkMode = vueProps.$localStorage.get(DARK_MODE, false) + const latestVersion = vueProps.$localStorage.get(LATEST_CS_VERSION, { version: '', fetchedTs: 0 }) const hasAuth = Object.keys(cachedApis).length > 0 commit('SET_DOMAIN_STORE', domainStore) commit('SET_DARK_MODE', darkMode) + commit('SET_LATEST_VERSION', latestVersion) if (hasAuth) { console.log('Login detected, using cached APIs') commit('SET_ZONES', cachedZones) @@ -294,6 +310,7 @@ const user = { const result = response.listusersresponse.user[0] commit('SET_INFO', result) commit('SET_NAME', result.firstname + ' ' + result.lastname) + store.dispatch('SetCsLatestVersion', result.rolename) resolve(cachedApis) }).catch(error => { reject(error) @@ -332,12 +349,41 @@ const user = { }).catch(error => { reject(error) }) + + api('listNetworks', { restartrequired: true, forvpc: false }).then(response => { + if (response.listnetworksresponse.count > 0) { + store.dispatch('AddHeaderNotice', { + key: 'NETWORK_RESTART_REQUIRED', + title: i18n.global.t('label.network.restart.required'), + description: i18n.global.t('message.network.restart.required'), + path: '/guestnetwork/', + query: { restartrequired: true, forvpc: false }, + status: 'done', + timestamp: new Date() + }) + } + }).catch(ignored => {}) + + api('listVPCs', { restartrequired: true }).then(response => { + if (response.listvpcsresponse.count > 0) { + store.dispatch('AddHeaderNotice', { + key: 'VPC_RESTART_REQUIRED', + title: i18n.global.t('label.vpc.restart.required'), + description: i18n.global.t('message.vpc.restart.required'), + path: '/vpc/', + query: { restartrequired: true }, + status: 'done', + timestamp: new Date() + }) + } + }).catch(ignored => {}) } api('listUsers', { username: Cookies.get('username') }).then(response => { const result = response.listusersresponse.user[0] commit('SET_INFO', result) commit('SET_NAME', result.firstname + ' ' + result.lastname) + store.dispatch('SetCsLatestVersion', result.rolename) }).catch(error => { reject(error) }) @@ -367,6 +413,8 @@ const user = { commit('SET_CLOUDIAN', cloudian) }).catch(ignored => { }) + }).catch(error => { + console.error(error) }) }, @@ -488,6 +536,29 @@ const user = { SetDomainStore ({ commit }, domainStore) { commit('SET_DOMAIN_STORE', domainStore) }, + SetCsLatestVersion ({ commit }, rolename) { + const lastFetchTs = store.getters.latestVersion?.fetchedTs ? store.getters.latestVersion.fetchedTs : 0 + if (rolename === 'Root Admin' && (+new Date() - lastFetchTs) > 24 * 60 * 60 * 1000) { + axios.get( + 'https://api.github.com/repos/apache/cloudstack/releases' + ).then(response => { + let latestReleaseVersion = getParsedVersion(response[0].tag_name) + let latestTag = response[0].tag_name + + for (const release of response) { + if (release.tag_name.toLowerCase().includes('rc')) { + continue + } + const parsedVersion = getParsedVersion(release.tag_name) + if (semver.gte(parsedVersion, latestReleaseVersion)) { + latestReleaseVersion = parsedVersion + latestTag = release.tag_name + commit('SET_LATEST_VERSION', { version: latestTag, fetchedTs: (+new Date()) }) + } + } + }).catch(ignored => {}) + } + }, SetDarkMode ({ commit }, darkMode) { commit('SET_DARK_MODE', darkMode) }, diff --git a/ui/src/store/mutation-types.js b/ui/src/store/mutation-types.js index 77aeb8fb7b6f..93dfc9fbc200 100644 --- a/ui/src/store/mutation-types.js +++ b/ui/src/store/mutation-types.js @@ -35,6 +35,7 @@ export const USE_BROWSER_TIMEZONE = 'USE_BROWSER_TIMEZONE' export const SERVER_MANAGER = 'SERVER_MANAGER' export const DOMAIN_STORE = 'DOMAIN_STORE' export const DARK_MODE = 'DARK_MODE' +export const LATEST_CS_VERSION = 'LATEST_CS_VERSION' export const VUE_VERSION = 'VUE_VERSION' export const CUSTOM_COLUMNS = 'CUSTOM_COLUMNS' export const RELOAD_ALL_PROJECTS = 'RELOAD_ALL_PROJECTS' diff --git a/ui/src/utils/util.js b/ui/src/utils/util.js index fad4d1e0f5dd..8773f07446e6 100644 --- a/ui/src/utils/util.js +++ b/ui/src/utils/util.js @@ -15,6 +15,8 @@ // specific language governing permissions and limitations // under the License. +import semver from 'semver' + export function timeFix () { const time = new Date() const hour = time.getHours() @@ -69,6 +71,14 @@ export function sanitizeReverse (value) { .replace(/>/g, '>') } +export function getParsedVersion (version) { + version = version.split('-')[0] + if (semver.valid(version) === null) { + version = version.split('.').slice(1).join('.') + } + return version +} + export function toCsv ({ keys = null, data = null, columnDelimiter = ',', lineDelimiter = '\n' }) { if (data === null || !data.length) { return null From bc393923515f9795f3f983ba4040e3f0a5326efe Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Wed, 4 Sep 2024 09:45:39 +0200 Subject: [PATCH 3/3] Fix PR lint error caused by deps/install-non-oss.sh (#9631) --- deps/install-non-oss.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/deps/install-non-oss.sh b/deps/install-non-oss.sh index f571af239bd2..56cfc2cba9cc 100755 --- a/deps/install-non-oss.sh +++ b/deps/install-non-oss.sh @@ -86,4 +86,3 @@ mvn install:install-file -Dfile=juniper-contrail-api-1.0-SNAPSHOT.jar -DgroupId= mvn install:install-file -Dfile=juniper-tungsten-api-2.0.jar -DgroupId=net.juniper.tungsten -DartifactId=juniper-tungsten-api -Dversion=2.0 -Dpackaging=jar exit 0 -